@@ -4,12 +4,14 @@ import (
4
4
"context"
5
5
"database/sql"
6
6
"encoding/json"
7
+ "errors"
7
8
"fmt"
8
9
"sort"
9
10
"testing"
10
11
"time"
11
12
12
13
"github.com/google/uuid"
14
+ "github.com/lib/pq"
13
15
"github.com/prometheus/client_golang/prometheus"
14
16
"github.com/stretchr/testify/assert"
15
17
"github.com/stretchr/testify/require"
@@ -4720,6 +4722,238 @@ func TestGetPresetsAtFailureLimit(t *testing.T) {
4720
4722
})
4721
4723
}
4722
4724
4725
+ func TestWorkspaceAgentNameUniqueTrigger (t * testing.T ) {
4726
+ t .Parallel ()
4727
+
4728
+ if ! dbtestutil .WillUsePostgres () {
4729
+ t .Skip ("This test makes use of a database trigger not implemented in dbmem" )
4730
+ }
4731
+
4732
+ createWorkspaceWithAgent := func (t * testing.T , db database.Store , org database.Organization , agentName string ) (database.WorkspaceBuild , database.WorkspaceResource , database.WorkspaceAgent ) {
4733
+ t .Helper ()
4734
+
4735
+ user := dbgen .User (t , db , database.User {})
4736
+ template := dbgen .Template (t , db , database.Template {
4737
+ OrganizationID : org .ID ,
4738
+ CreatedBy : user .ID ,
4739
+ })
4740
+ templateVersion := dbgen .TemplateVersion (t , db , database.TemplateVersion {
4741
+ TemplateID : uuid.NullUUID {Valid : true , UUID : template .ID },
4742
+ OrganizationID : org .ID ,
4743
+ CreatedBy : user .ID ,
4744
+ })
4745
+ workspace := dbgen .Workspace (t , db , database.WorkspaceTable {
4746
+ OrganizationID : org .ID ,
4747
+ TemplateID : template .ID ,
4748
+ OwnerID : user .ID ,
4749
+ })
4750
+ job := dbgen .ProvisionerJob (t , db , nil , database.ProvisionerJob {
4751
+ Type : database .ProvisionerJobTypeWorkspaceBuild ,
4752
+ OrganizationID : org .ID ,
4753
+ })
4754
+ build := dbgen .WorkspaceBuild (t , db , database.WorkspaceBuild {
4755
+ BuildNumber : 1 ,
4756
+ JobID : job .ID ,
4757
+ WorkspaceID : workspace .ID ,
4758
+ TemplateVersionID : templateVersion .ID ,
4759
+ })
4760
+ resource := dbgen .WorkspaceResource (t , db , database.WorkspaceResource {
4761
+ JobID : build .JobID ,
4762
+ })
4763
+ agent := dbgen .WorkspaceAgent (t , db , database.WorkspaceAgent {
4764
+ ResourceID : resource .ID ,
4765
+ Name : agentName ,
4766
+ })
4767
+
4768
+ return build , resource , agent
4769
+ }
4770
+
4771
+ t .Run ("DuplicateNamesInSameWorkspaceResource" , func (t * testing.T ) {
4772
+ t .Parallel ()
4773
+
4774
+ db , _ := dbtestutil .NewDB (t )
4775
+ org := dbgen .Organization (t , db , database.Organization {})
4776
+ ctx := testutil .Context (t , testutil .WaitShort )
4777
+
4778
+ // Given: A workspace with an agent
4779
+ _ , resource , _ := createWorkspaceWithAgent (t , db , org , "duplicate-agent" )
4780
+
4781
+ // When: Another agent is created for that workspace with the same name.
4782
+ _ , err := db .InsertWorkspaceAgent (ctx , database.InsertWorkspaceAgentParams {
4783
+ ID : uuid .New (),
4784
+ CreatedAt : time .Now (),
4785
+ UpdatedAt : time .Now (),
4786
+ Name : "duplicate-agent" , // Same name as agent1
4787
+ ResourceID : resource .ID ,
4788
+ AuthToken : uuid .New (),
4789
+ Architecture : "amd64" ,
4790
+ OperatingSystem : "linux" ,
4791
+ APIKeyScope : database .AgentKeyScopeEnumAll ,
4792
+ })
4793
+
4794
+ // Then: We expect it to fail.
4795
+ require .Error (t , err )
4796
+ var pqErr * pq.Error
4797
+ require .True (t , errors .As (err , & pqErr ))
4798
+ require .Equal (t , pq .ErrorCode ("23505" ), pqErr .Code ) // unique_violation
4799
+ require .Contains (t , pqErr .Message , `workspace agent name "duplicate-agent" already exists in this workspace build` )
4800
+ })
4801
+
4802
+ t .Run ("DuplicateNamesInSameProvisionerJob" , func (t * testing.T ) {
4803
+ t .Parallel ()
4804
+
4805
+ db , _ := dbtestutil .NewDB (t )
4806
+ org := dbgen .Organization (t , db , database.Organization {})
4807
+ ctx := testutil .Context (t , testutil .WaitShort )
4808
+
4809
+ // Given: A workspace with an agent
4810
+ _ , resource , agent := createWorkspaceWithAgent (t , db , org , "duplicate-agent" )
4811
+
4812
+ // When: A child agent is created for that workspace with the same name.
4813
+ _ , err := db .InsertWorkspaceAgent (ctx , database.InsertWorkspaceAgentParams {
4814
+ ID : uuid .New (),
4815
+ CreatedAt : time .Now (),
4816
+ UpdatedAt : time .Now (),
4817
+ Name : agent .Name ,
4818
+ ResourceID : resource .ID ,
4819
+ AuthToken : uuid .New (),
4820
+ Architecture : "amd64" ,
4821
+ OperatingSystem : "linux" ,
4822
+ APIKeyScope : database .AgentKeyScopeEnumAll ,
4823
+ })
4824
+
4825
+ // Then: We expect it to fail.
4826
+ require .Error (t , err )
4827
+ var pqErr * pq.Error
4828
+ require .True (t , errors .As (err , & pqErr ))
4829
+ require .Equal (t , pq .ErrorCode ("23505" ), pqErr .Code ) // unique_violation
4830
+ require .Contains (t , pqErr .Message , `workspace agent name "duplicate-agent" already exists in this workspace build` )
4831
+ })
4832
+
4833
+ t .Run ("DuplicateChildNamesOverMultipleResources" , func (t * testing.T ) {
4834
+ t .Parallel ()
4835
+
4836
+ db , _ := dbtestutil .NewDB (t )
4837
+ org := dbgen .Organization (t , db , database.Organization {})
4838
+ ctx := testutil .Context (t , testutil .WaitShort )
4839
+
4840
+ // Given: A workspace with two agents
4841
+ _ , resource1 , agent1 := createWorkspaceWithAgent (t , db , org , "parent-agent-1" )
4842
+
4843
+ resource2 := dbgen .WorkspaceResource (t , db , database.WorkspaceResource {JobID : resource1 .JobID })
4844
+ agent2 := dbgen .WorkspaceAgent (t , db , database.WorkspaceAgent {
4845
+ ResourceID : resource2 .ID ,
4846
+ Name : "parent-agent-2" ,
4847
+ })
4848
+
4849
+ // Given: One agent has a child agent
4850
+ agent1Child := dbgen .WorkspaceAgent (t , db , database.WorkspaceAgent {
4851
+ ParentID : uuid.NullUUID {Valid : true , UUID : agent1 .ID },
4852
+ Name : "child-agent" ,
4853
+ ResourceID : resource1 .ID ,
4854
+ })
4855
+
4856
+ // When: A child agent is inserted for the other parent.
4857
+ _ , err := db .InsertWorkspaceAgent (ctx , database.InsertWorkspaceAgentParams {
4858
+ ID : uuid .New (),
4859
+ ParentID : uuid.NullUUID {Valid : true , UUID : agent2 .ID },
4860
+ CreatedAt : time .Now (),
4861
+ UpdatedAt : time .Now (),
4862
+ Name : agent1Child .Name ,
4863
+ ResourceID : resource2 .ID ,
4864
+ AuthToken : uuid .New (),
4865
+ Architecture : "amd64" ,
4866
+ OperatingSystem : "linux" ,
4867
+ APIKeyScope : database .AgentKeyScopeEnumAll ,
4868
+ })
4869
+
4870
+ // Then: We expect it to fail.
4871
+ require .Error (t , err )
4872
+ var pqErr * pq.Error
4873
+ require .True (t , errors .As (err , & pqErr ))
4874
+ require .Equal (t , pq .ErrorCode ("23505" ), pqErr .Code ) // unique_violation
4875
+ require .Contains (t , pqErr .Message , `workspace agent name "child-agent" already exists in this workspace build` )
4876
+ })
4877
+
4878
+ t .Run ("SameNamesInDifferentWorkspaces" , func (t * testing.T ) {
4879
+ t .Parallel ()
4880
+
4881
+ agentName := "same-name-different-workspace"
4882
+
4883
+ db , _ := dbtestutil .NewDB (t )
4884
+ org := dbgen .Organization (t , db , database.Organization {})
4885
+
4886
+ // Given: A workspace with an agent
4887
+ _ , _ , agent1 := createWorkspaceWithAgent (t , db , org , agentName )
4888
+ require .Equal (t , agentName , agent1 .Name )
4889
+
4890
+ // When: A second workspace is created with an agent having the same name
4891
+ _ , _ , agent2 := createWorkspaceWithAgent (t , db , org , agentName )
4892
+ require .Equal (t , agentName , agent2 .Name )
4893
+
4894
+ // Then: We expect there to be different agents with the same name.
4895
+ require .NotEqual (t , agent1 .ID , agent2 .ID )
4896
+ require .Equal (t , agent1 .Name , agent2 .Name )
4897
+ })
4898
+
4899
+ t .Run ("NullWorkspaceID" , func (t * testing.T ) {
4900
+ t .Parallel ()
4901
+
4902
+ db , _ := dbtestutil .NewDB (t )
4903
+ org := dbgen .Organization (t , db , database.Organization {})
4904
+ ctx := testutil .Context (t , testutil .WaitShort )
4905
+
4906
+ // Given: A resource that does not belong to a workspace build (simulating template import)
4907
+ orphanJob := dbgen .ProvisionerJob (t , db , nil , database.ProvisionerJob {
4908
+ Type : database .ProvisionerJobTypeTemplateVersionImport ,
4909
+ OrganizationID : org .ID ,
4910
+ })
4911
+ orphanResource := dbgen .WorkspaceResource (t , db , database.WorkspaceResource {
4912
+ JobID : orphanJob .ID ,
4913
+ })
4914
+
4915
+ // And this resource has a workspace agent.
4916
+ agent1 , err := db .InsertWorkspaceAgent (ctx , database.InsertWorkspaceAgentParams {
4917
+ ID : uuid .New (),
4918
+ CreatedAt : time .Now (),
4919
+ UpdatedAt : time .Now (),
4920
+ Name : "orphan-agent" ,
4921
+ ResourceID : orphanResource .ID ,
4922
+ AuthToken : uuid .New (),
4923
+ Architecture : "amd64" ,
4924
+ OperatingSystem : "linux" ,
4925
+ APIKeyScope : database .AgentKeyScopeEnumAll ,
4926
+ })
4927
+ require .NoError (t , err )
4928
+ require .Equal (t , "orphan-agent" , agent1 .Name )
4929
+
4930
+ // When: We created another resource that does not belong to a workspace build.
4931
+ orphanJob2 := dbgen .ProvisionerJob (t , db , nil , database.ProvisionerJob {
4932
+ Type : database .ProvisionerJobTypeTemplateVersionImport ,
4933
+ OrganizationID : org .ID ,
4934
+ })
4935
+ orphanResource2 := dbgen .WorkspaceResource (t , db , database.WorkspaceResource {
4936
+ JobID : orphanJob2 .ID ,
4937
+ })
4938
+
4939
+ // Then: We expect to be able to create an agent in this new resource that has the same name.
4940
+ agent2 , err := db .InsertWorkspaceAgent (ctx , database.InsertWorkspaceAgentParams {
4941
+ ID : uuid .New (),
4942
+ CreatedAt : time .Now (),
4943
+ UpdatedAt : time .Now (),
4944
+ Name : "orphan-agent" , // Same name as agent1
4945
+ ResourceID : orphanResource2 .ID ,
4946
+ AuthToken : uuid .New (),
4947
+ Architecture : "amd64" ,
4948
+ OperatingSystem : "linux" ,
4949
+ APIKeyScope : database .AgentKeyScopeEnumAll ,
4950
+ })
4951
+ require .NoError (t , err )
4952
+ require .Equal (t , "orphan-agent" , agent2 .Name )
4953
+ require .NotEqual (t , agent1 .ID , agent2 .ID )
4954
+ })
4955
+ }
4956
+
4723
4957
func requireUsersMatch (t testing.TB , expected []database.User , found []database.GetUsersRow , msg string ) {
4724
4958
t .Helper ()
4725
4959
require .ElementsMatch (t , expected , database .ConvertUserRows (found ), msg )
0 commit comments