@@ -4,12 +4,15 @@ import (
4
4
"context"
5
5
"database/sql"
6
6
"encoding/json"
7
+ "errors"
7
8
"fmt"
8
9
"sort"
10
+ "sync/atomic"
9
11
"testing"
10
12
"time"
11
13
12
14
"github.com/google/uuid"
15
+ "github.com/lib/pq"
13
16
"github.com/prometheus/client_golang/prometheus"
14
17
"github.com/stretchr/testify/assert"
15
18
"github.com/stretchr/testify/require"
@@ -4705,6 +4708,178 @@ func TestGetPresetsAtFailureLimit(t *testing.T) {
4705
4708
})
4706
4709
}
4707
4710
4711
+ func TestWorkspaceAgentNameUniqueTrigger (t * testing.T ) {
4712
+ t .Parallel ()
4713
+
4714
+ var builds atomic.Int32
4715
+
4716
+ if ! dbtestutil .WillUsePostgres () {
4717
+ t .Skip ("This test makes use of a database trigger not implemented in dbmem" )
4718
+ }
4719
+
4720
+ createWorkspaceWithAgent := func (t * testing.T ,db database.Store ,org database.Organization ,agentName string ) (database.WorkspaceTable , database.TemplateVersion , database.WorkspaceAgent ) {
4721
+ t .Helper ()
4722
+
4723
+ user := dbgen .User (t ,db , database.User {})
4724
+ template := dbgen .Template (t ,db , database.Template {
4725
+ OrganizationID :org .ID ,
4726
+ CreatedBy :user .ID ,
4727
+ })
4728
+ templateVersion := dbgen .TemplateVersion (t ,db , database.TemplateVersion {
4729
+ TemplateID : uuid.NullUUID {Valid :true ,UUID :template .ID },
4730
+ OrganizationID :org .ID ,
4731
+ CreatedBy :user .ID ,
4732
+ })
4733
+ workspace := dbgen .Workspace (t ,db , database.WorkspaceTable {
4734
+ OrganizationID :org .ID ,
4735
+ TemplateID :template .ID ,
4736
+ OwnerID :user .ID ,
4737
+ })
4738
+ job := dbgen .ProvisionerJob (t ,db ,nil , database.ProvisionerJob {
4739
+ Type :database .ProvisionerJobTypeWorkspaceBuild ,
4740
+ OrganizationID :org .ID ,
4741
+ })
4742
+ build := dbgen .WorkspaceBuild (t ,db , database.WorkspaceBuild {
4743
+ BuildNumber :builds .Add (1 ),
4744
+ JobID :job .ID ,
4745
+ WorkspaceID :workspace .ID ,
4746
+ TemplateVersionID :templateVersion .ID ,
4747
+ })
4748
+ resource := dbgen .WorkspaceResource (t ,db , database.WorkspaceResource {
4749
+ JobID :build .JobID ,
4750
+ })
4751
+ agent := dbgen .WorkspaceAgent (t ,db , database.WorkspaceAgent {
4752
+ ResourceID :resource .ID ,
4753
+ Name :agentName ,
4754
+ })
4755
+
4756
+ return workspace ,templateVersion ,agent
4757
+ }
4758
+
4759
+ t .Run ("DuplicateNamesInSameWorkspace" ,func (t * testing.T ) {
4760
+ t .Parallel ()
4761
+
4762
+ db ,_ := dbtestutil .NewDB (t )
4763
+ org := dbgen .Organization (t ,db , database.Organization {})
4764
+ ctx := testutil .Context (t ,testutil .WaitShort )
4765
+
4766
+ // Given: A workspace with an agent
4767
+ workspace1 ,templateVersion1 ,agent1 := createWorkspaceWithAgent (t ,db ,org ,"duplicate-agent" )
4768
+ require .Equal (t ,"duplicate-agent" ,agent1 .Name )
4769
+
4770
+ // When: Another agent is created for that workspace with the same name.
4771
+ job2 := dbgen .ProvisionerJob (t ,db ,nil , database.ProvisionerJob {
4772
+ Type :database .ProvisionerJobTypeWorkspaceBuild ,
4773
+ OrganizationID :org .ID ,
4774
+ })
4775
+ build2 := dbgen .WorkspaceBuild (t ,db , database.WorkspaceBuild {
4776
+ BuildNumber :builds .Add (1 ),
4777
+ JobID :job2 .ID ,
4778
+ WorkspaceID :workspace1 .ID ,
4779
+ TemplateVersionID :templateVersion1 .ID ,
4780
+ })
4781
+ resource2 := dbgen .WorkspaceResource (t ,db , database.WorkspaceResource {
4782
+ JobID :build2 .JobID ,
4783
+ })
4784
+ _ ,err := db .InsertWorkspaceAgent (ctx , database.InsertWorkspaceAgentParams {
4785
+ ID :uuid .New (),
4786
+ CreatedAt :time .Now (),
4787
+ UpdatedAt :time .Now (),
4788
+ Name :"duplicate-agent" ,// Same name as agent1
4789
+ ResourceID :resource2 .ID ,
4790
+ AuthToken :uuid .New (),
4791
+ Architecture :"amd64" ,
4792
+ OperatingSystem :"linux" ,
4793
+ APIKeyScope :database .AgentKeyScopeEnumAll ,
4794
+ })
4795
+
4796
+ // Then: We expect it to fail.
4797
+ require .Error (t ,err )
4798
+ var pqErr * pq.Error
4799
+ require .True (t ,errors .As (err ,& pqErr ))
4800
+ require .Equal (t ,pq .ErrorCode ("23505" ),pqErr .Code )// unique_violation
4801
+ require .Contains (t ,pqErr .Message ,`workspace agent name "duplicate-agent" already exists in this workspace` )
4802
+ })
4803
+
4804
+ t .Run ("SameNamesInDifferentWorkspaces" ,func (t * testing.T ) {
4805
+ t .Parallel ()
4806
+
4807
+ agentName := "same-name-different-workspace"
4808
+
4809
+ db ,_ := dbtestutil .NewDB (t )
4810
+ org := dbgen .Organization (t ,db , database.Organization {})
4811
+
4812
+ // Given: A workspace with an agent
4813
+ _ ,_ ,agent1 := createWorkspaceWithAgent (t ,db ,org ,agentName )
4814
+ require .Equal (t ,agentName ,agent1 .Name )
4815
+
4816
+ // When: A second workspace is created with an agent having the same name
4817
+ _ ,_ ,agent2 := createWorkspaceWithAgent (t ,db ,org ,agentName )
4818
+ require .Equal (t ,agentName ,agent2 .Name )
4819
+
4820
+ // Then: We expect there to be different agents with the same name.
4821
+ require .NotEqual (t ,agent1 .ID ,agent2 .ID )
4822
+ require .Equal (t ,agent1 .Name ,agent2 .Name )
4823
+ })
4824
+
4825
+ t .Run ("NullWorkspaceID" ,func (t * testing.T ) {
4826
+ t .Parallel ()
4827
+
4828
+ db ,_ := dbtestutil .NewDB (t )
4829
+ org := dbgen .Organization (t ,db , database.Organization {})
4830
+ ctx := testutil .Context (t ,testutil .WaitShort )
4831
+
4832
+ // Given: A resource that does not belong to a workspace build (simulating template import)
4833
+ orphanJob := dbgen .ProvisionerJob (t ,db ,nil , database.ProvisionerJob {
4834
+ Type :database .ProvisionerJobTypeTemplateVersionImport ,
4835
+ OrganizationID :org .ID ,
4836
+ })
4837
+ orphanResource := dbgen .WorkspaceResource (t ,db , database.WorkspaceResource {
4838
+ JobID :orphanJob .ID ,
4839
+ })
4840
+
4841
+ // And this resource has a workspace agent.
4842
+ agent1 ,err := db .InsertWorkspaceAgent (ctx , database.InsertWorkspaceAgentParams {
4843
+ ID :uuid .New (),
4844
+ CreatedAt :time .Now (),
4845
+ UpdatedAt :time .Now (),
4846
+ Name :"orphan-agent" ,
4847
+ ResourceID :orphanResource .ID ,
4848
+ AuthToken :uuid .New (),
4849
+ Architecture :"amd64" ,
4850
+ OperatingSystem :"linux" ,
4851
+ APIKeyScope :database .AgentKeyScopeEnumAll ,
4852
+ })
4853
+ require .NoError (t ,err )
4854
+ require .Equal (t ,"orphan-agent" ,agent1 .Name )
4855
+
4856
+ // When: We created another resource that does not belong to a workspace build.
4857
+ orphanJob2 := dbgen .ProvisionerJob (t ,db ,nil , database.ProvisionerJob {
4858
+ Type :database .ProvisionerJobTypeTemplateVersionImport ,
4859
+ OrganizationID :org .ID ,
4860
+ })
4861
+ orphanResource2 := dbgen .WorkspaceResource (t ,db , database.WorkspaceResource {
4862
+ JobID :orphanJob2 .ID ,
4863
+ })
4864
+
4865
+ // Then: We expect to be able to create an agent in this new resource that has the same name.
4866
+ agent2 ,err := db .InsertWorkspaceAgent (ctx , database.InsertWorkspaceAgentParams {
4867
+ ID :uuid .New (),
4868
+ CreatedAt :time .Now (),
4869
+ UpdatedAt :time .Now (),
4870
+ Name :"orphan-agent" ,// Same name as agent1
4871
+ ResourceID :orphanResource2 .ID ,
4872
+ AuthToken :uuid .New (),
4873
+ Architecture :"amd64" ,
4874
+ OperatingSystem :"linux" ,
4875
+ APIKeyScope :database .AgentKeyScopeEnumAll ,
4876
+ })
4877
+ require .NoError (t ,err )
4878
+ require .Equal (t ,"orphan-agent" ,agent2 .Name )
4879
+ require .NotEqual (t ,agent1 .ID ,agent2 .ID )
4880
+ })
4881
+ }
4882
+
4708
4883
func requireUsersMatch (t testing.TB ,expected []database.User ,found []database.GetUsersRow ,msg string ) {
4709
4884
t .Helper ()
4710
4885
require .ElementsMatch (t ,expected ,database .ConvertUserRows (found ),msg )