@@ -1840,6 +1840,68 @@ func TestPostWorkspaceBuild(t *testing.T) {
18401840 require .NoError (t , err )
18411841 require .Equal (t , codersdk .BuildReasonDashboard , build .Reason )
18421842 })
1843+ t .Run ("DeletedWorkspace" , func (t * testing.T ) {
1844+ t .Parallel ()
1845+
1846+ // Given: a workspace that has already been deleted
1847+ var (
1848+ ctx = testutil .Context (t , testutil .WaitShort )
1849+ logger = slogtest .Make (t , & slogtest.Options {}).Leveled (slog .LevelError )
1850+ adminClient , db = coderdtest .NewWithDatabase (t , & coderdtest.Options {
1851+ Logger : & logger ,
1852+ })
1853+ admin = coderdtest .CreateFirstUser (t , adminClient )
1854+ workspaceOwnerClient , member1 = coderdtest .CreateAnotherUser (t , adminClient , admin .OrganizationID )
1855+ otherMemberClient , _ = coderdtest .CreateAnotherUser (t , adminClient , admin .OrganizationID )
1856+ ws = dbfake .WorkspaceBuild (t , db , database.WorkspaceTable {OwnerID : member1 .ID , OrganizationID : admin .OrganizationID }).
1857+ Seed (database.WorkspaceBuild {Transition : database .WorkspaceTransitionDelete }).
1858+ Do ()
1859+ )
1860+
1861+ // This needs to be done separately as provisionerd handles marking the workspace as deleted
1862+ // and we're skipping provisionerd here for speed.
1863+ require .NoError (t , db .UpdateWorkspaceDeletedByID (dbauthz .AsProvisionerd (ctx ), database.UpdateWorkspaceDeletedByIDParams {
1864+ ID : ws .Workspace .ID ,
1865+ Deleted : true ,
1866+ }))
1867+
1868+ // Assert test invariant: Workspace should be deleted
1869+ dbWs , err := db .GetWorkspaceByID (dbauthz .AsProvisionerd (ctx ), ws .Workspace .ID )
1870+ require .NoError (t , err )
1871+ require .True (t , dbWs .Deleted , "workspace should be deleted" )
1872+
1873+ for _ , tc := range []struct {
1874+ user * codersdk.Client
1875+ tr codersdk.WorkspaceTransition
1876+ expectStatus int
1877+ }{
1878+ // You should not be allowed to mess with a workspace you don't own, regardless of its deleted state.
1879+ {otherMemberClient , codersdk .WorkspaceTransitionStart , http .StatusNotFound },
1880+ {otherMemberClient , codersdk .WorkspaceTransitionStop , http .StatusNotFound },
1881+ {otherMemberClient , codersdk .WorkspaceTransitionDelete , http .StatusNotFound },
1882+ // Starting or stopping a workspace is not allowed when it is deleted.
1883+ {workspaceOwnerClient , codersdk .WorkspaceTransitionStart , http .StatusConflict },
1884+ {workspaceOwnerClient , codersdk .WorkspaceTransitionStop , http .StatusConflict },
1885+ // We allow a delete just in case a retry is required. In most cases, this will be a no-op.
1886+ // Note: this is the last test case because it will change the state of the workspace.
1887+ {workspaceOwnerClient , codersdk .WorkspaceTransitionDelete , http .StatusOK },
1888+ } {
1889+ // When: we create a workspace build with the given transition
1890+ _ , err = tc .user .CreateWorkspaceBuild (ctx , ws .Workspace .ID , codersdk.CreateWorkspaceBuildRequest {
1891+ Transition : tc .tr ,
1892+ })
1893+
1894+ // Then: we allow ONLY a delete build for a deleted workspace.
1895+ if tc .expectStatus < http .StatusBadRequest {
1896+ require .NoError (t , err , "creating a %s build for a deleted workspace should not error" , tc .tr )
1897+ } else {
1898+ var apiError * codersdk.Error
1899+ require .Error (t , err , "creating a %s build for a deleted workspace should return an error" , tc .tr )
1900+ require .ErrorAs (t , err , & apiError )
1901+ require .Equal (t , tc .expectStatus , apiError .StatusCode ())
1902+ }
1903+ }
1904+ })
18431905}
18441906
18451907func TestWorkspaceBuildTimings (t * testing.T ) {
0 commit comments