11package coderd_test
22
33import (
4+ "bytes"
45"context"
56"database/sql"
67"errors"
@@ -25,6 +26,7 @@ import (
2526"github.com/coder/coder/v2/coderd/coderdtest/oidctest"
2627"github.com/coder/coder/v2/coderd/database"
2728"github.com/coder/coder/v2/coderd/database/dbauthz"
29+ "github.com/coder/coder/v2/coderd/database/dbfake"
2830"github.com/coder/coder/v2/coderd/database/dbgen"
2931"github.com/coder/coder/v2/coderd/database/dbtestutil"
3032"github.com/coder/coder/v2/coderd/database/dbtime"
@@ -371,42 +373,174 @@ func TestWorkspaceBuildsProvisionerState(t *testing.T) {
371373
372374t .Run ("Orphan" ,func (t * testing.T ) {
373375t .Parallel ()
374- client := coderdtest .New (t ,& coderdtest.Options {IncludeProvisionerDaemon :true })
375- first := coderdtest .CreateFirstUser (t ,client )
376-
377- ctx ,cancel := context .WithTimeout (context .Background (),testutil .WaitLong )
378- defer cancel ()
379376
380- version := coderdtest .CreateTemplateVersion (t ,client ,first .OrganizationID ,nil )
381- template := coderdtest .CreateTemplate (t ,client ,first .OrganizationID ,version .ID )
382- coderdtest .AwaitTemplateVersionJobCompleted (t ,client ,version .ID )
377+ t .Run ("WithoutDelete" ,func (t * testing.T ) {
378+ t .Parallel ()
379+ client ,store := coderdtest .NewWithDatabase (t ,nil )
380+ first := coderdtest .CreateFirstUser (t ,client )
381+ templateAdmin ,templateAdminUser := coderdtest .CreateAnotherUser (t ,client ,first .OrganizationID ,rbac .RoleTemplateAdmin ())
382+
383+ r := dbfake .WorkspaceBuild (t ,store , database.WorkspaceTable {
384+ OwnerID :templateAdminUser .ID ,
385+ OrganizationID :first .OrganizationID ,
386+ }).Do ()
387+
388+ ctx ,cancel := context .WithTimeout (context .Background (),testutil .WaitLong )
389+ defer cancel ()
390+
391+ // Trying to orphan without delete transition fails.
392+ _ ,err := templateAdmin .CreateWorkspaceBuild (ctx ,r .Workspace .ID , codersdk.CreateWorkspaceBuildRequest {
393+ TemplateVersionID :r .TemplateVersion .ID ,
394+ Transition :codersdk .WorkspaceTransitionStart ,
395+ Orphan :true ,
396+ })
397+ require .Error (t ,err ,"Orphan is only permitted when deleting a workspace." )
398+ cerr := coderdtest .SDKError (t ,err )
399+ require .Equal (t ,http .StatusBadRequest ,cerr .StatusCode ())
400+ })
383401
384- workspace := coderdtest .CreateWorkspace (t ,client ,template .ID )
385- coderdtest .AwaitWorkspaceBuildJobCompleted (t ,client ,workspace .LatestBuild .ID )
402+ t .Run ("WithState" ,func (t * testing.T ) {
403+ t .Parallel ()
404+ client ,store := coderdtest .NewWithDatabase (t ,nil )
405+ first := coderdtest .CreateFirstUser (t ,client )
406+ templateAdmin ,templateAdminUser := coderdtest .CreateAnotherUser (t ,client ,first .OrganizationID ,rbac .RoleTemplateAdmin ())
407+
408+ r := dbfake .WorkspaceBuild (t ,store , database.WorkspaceTable {
409+ OwnerID :templateAdminUser .ID ,
410+ OrganizationID :first .OrganizationID ,
411+ }).Do ()
412+
413+ ctx ,cancel := context .WithTimeout (context .Background (),testutil .WaitLong )
414+ defer cancel ()
415+
416+ // Providing both state and orphan fails.
417+ _ ,err := templateAdmin .CreateWorkspaceBuild (ctx ,r .Workspace .ID , codersdk.CreateWorkspaceBuildRequest {
418+ TemplateVersionID :r .TemplateVersion .ID ,
419+ Transition :codersdk .WorkspaceTransitionDelete ,
420+ ProvisionerState : []byte (" " ),
421+ Orphan :true ,
422+ })
423+ require .Error (t ,err )
424+ cerr := coderdtest .SDKError (t ,err )
425+ require .Equal (t ,http .StatusBadRequest ,cerr .StatusCode ())
426+ })
386427
387- // Providing both state and orphan fails.
388- _ ,err := client .CreateWorkspaceBuild (ctx ,workspace .ID , codersdk.CreateWorkspaceBuildRequest {
389- TemplateVersionID :workspace .LatestBuild .TemplateVersionID ,
390- Transition :codersdk .WorkspaceTransitionDelete ,
391- ProvisionerState : []byte (" " ),
392- Orphan :true ,
428+ t .Run ("NoPermission" ,func (t * testing.T ) {
429+ t .Parallel ()
430+ client ,store := coderdtest .NewWithDatabase (t ,nil )
431+ first := coderdtest .CreateFirstUser (t ,client )
432+ member ,memberUser := coderdtest .CreateAnotherUser (t ,client ,first .OrganizationID )
433+
434+ r := dbfake .WorkspaceBuild (t ,store , database.WorkspaceTable {
435+ OwnerID :memberUser .ID ,
436+ OrganizationID :first .OrganizationID ,
437+ }).Do ()
438+
439+ ctx ,cancel := context .WithTimeout (context .Background (),testutil .WaitLong )
440+ defer cancel ()
441+
442+ // Trying to orphan without being a template admin fails.
443+ _ ,err := member .CreateWorkspaceBuild (ctx ,r .Workspace .ID , codersdk.CreateWorkspaceBuildRequest {
444+ TemplateVersionID :r .TemplateVersion .ID ,
445+ Transition :codersdk .WorkspaceTransitionDelete ,
446+ Orphan :true ,
447+ })
448+ require .Error (t ,err )
449+ cerr := coderdtest .SDKError (t ,err )
450+ require .Equal (t ,http .StatusForbidden ,cerr .StatusCode ())
393451})
394- require .Error (t ,err )
395- cerr := coderdtest .SDKError (t ,err )
396- require .Equal (t ,http .StatusBadRequest ,cerr .StatusCode ())
397452
398- // Regular orphan operation succeeds.
399- build ,err := client .CreateWorkspaceBuild (ctx ,workspace .ID , codersdk.CreateWorkspaceBuildRequest {
400- TemplateVersionID :workspace .LatestBuild .TemplateVersionID ,
401- Transition :codersdk .WorkspaceTransitionDelete ,
402- Orphan :true ,
453+ t .Run ("OK" ,func (t * testing.T ) {
454+ // Include a provisioner so that we can test that provisionerdserver
455+ // performs deletion.
456+ auditor := audit .NewMock ()
457+ client ,store := coderdtest .NewWithDatabase (t ,& coderdtest.Options {IncludeProvisionerDaemon :true ,Auditor :auditor })
458+ first := coderdtest .CreateFirstUser (t ,client )
459+ templateAdmin ,templateAdminUser := coderdtest .CreateAnotherUser (t ,client ,first .OrganizationID ,rbac .RoleTemplateAdmin ())
460+
461+ ctx ,cancel := context .WithTimeout (context .Background (),testutil .WaitLong )
462+ defer cancel ()
463+ // This is a valid zip file. Without this the job will fail to complete.
464+ // TODO: add this to dbfake by default.
465+ zipBytes := make ([]byte ,22 )
466+ zipBytes [0 ]= 80
467+ zipBytes [1 ]= 75
468+ zipBytes [2 ]= 0o5
469+ zipBytes [3 ]= 0o6
470+ uploadRes ,err := client .Upload (ctx ,codersdk .ContentTypeZip ,bytes .NewReader (zipBytes ))
471+ require .NoError (t ,err )
472+
473+ tv := dbfake .TemplateVersion (t ,store ).
474+ FileID (uploadRes .ID ).
475+ Seed (database.TemplateVersion {
476+ OrganizationID :first .OrganizationID ,
477+ CreatedBy :templateAdminUser .ID ,
478+ }).
479+ Do ()
480+
481+ r := dbfake .WorkspaceBuild (t ,store , database.WorkspaceTable {
482+ OwnerID :templateAdminUser .ID ,
483+ OrganizationID :first .OrganizationID ,
484+ TemplateID :tv .Template .ID ,
485+ }).Do ()
486+
487+ auditor .ResetLogs ()
488+ // Regular orphan operation succeeds.
489+ build ,err := templateAdmin .CreateWorkspaceBuild (ctx ,r .Workspace .ID , codersdk.CreateWorkspaceBuildRequest {
490+ TemplateVersionID :r .TemplateVersion .ID ,
491+ Transition :codersdk .WorkspaceTransitionDelete ,
492+ Orphan :true ,
493+ })
494+ require .NoError (t ,err )
495+ coderdtest .AwaitWorkspaceBuildJobCompleted (t ,client ,build .ID )
496+
497+ // Validate that the deletion was audited.
498+ require .True (t ,auditor .Contains (t , database.AuditLog {
499+ ResourceID :build .ID ,
500+ Action :database .AuditActionDelete ,
501+ }))
403502})
404- require .NoError (t ,err )
405- coderdtest .AwaitWorkspaceBuildJobCompleted (t ,client ,build .ID )
406503
407- _ ,err = client .Workspace (ctx ,workspace .ID )
408- require .Error (t ,err )
409- require .Equal (t ,http .StatusGone ,coderdtest .SDKError (t ,err ).StatusCode ())
504+ t .Run ("NoProvisioners" ,func (t * testing.T ) {
505+ t .Parallel ()
506+ auditor := audit .NewMock ()
507+ client ,store := coderdtest .NewWithDatabase (t ,& coderdtest.Options {Auditor :auditor })
508+ first := coderdtest .CreateFirstUser (t ,client )
509+ templateAdmin ,templateAdminUser := coderdtest .CreateAnotherUser (t ,client ,first .OrganizationID ,rbac .RoleTemplateAdmin ())
510+
511+ ctx ,cancel := context .WithTimeout (context .Background (),testutil .WaitLong )
512+ defer cancel ()
513+ r := dbfake .WorkspaceBuild (t ,store , database.WorkspaceTable {
514+ OwnerID :templateAdminUser .ID ,
515+ OrganizationID :first .OrganizationID ,
516+ }).Do ()
517+
518+ // nolint:gocritic // For testing
519+ daemons ,err := store .GetProvisionerDaemons (dbauthz .AsSystemReadProvisionerDaemons (ctx ))
520+ require .NoError (t ,err )
521+ require .Empty (t ,daemons ,"Provisioner daemons should be empty for this test" )
522+
523+ // Orphan deletion still succeeds despite no provisioners being available.
524+ build ,err := templateAdmin .CreateWorkspaceBuild (ctx ,r .Workspace .ID , codersdk.CreateWorkspaceBuildRequest {
525+ TemplateVersionID :r .TemplateVersion .ID ,
526+ Transition :codersdk .WorkspaceTransitionDelete ,
527+ Orphan :true ,
528+ })
529+ require .NoError (t ,err )
530+ require .Equal (t ,codersdk .WorkspaceTransitionDelete ,build .Transition )
531+ require .Equal (t ,codersdk .ProvisionerJobSucceeded ,build .Job .Status )
532+ require .Empty (t ,build .Job .Error )
533+
534+ ws ,err := client .Workspace (ctx ,r .Workspace .ID )
535+ require .Empty (t ,ws )
536+ require .Equal (t ,http .StatusGone ,coderdtest .SDKError (t ,err ).StatusCode ())
537+
538+ // Validate that the deletion was audited.
539+ require .True (t ,auditor .Contains (t , database.AuditLog {
540+ ResourceID :build .ID ,
541+ Action :database .AuditActionDelete ,
542+ }))
543+ })
410544})
411545}
412546