@@ -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 )
0 commit comments