2022-01-25 19:52:58 +00:00
package coderd_test
import (
2023-06-05 23:12:10 +00:00
"bytes"
2022-01-25 19:52:58 +00:00
"context"
2023-06-05 23:12:10 +00:00
"database/sql"
"encoding/json"
2022-06-14 16:21:30 +00:00
"fmt"
2024-03-05 14:05:15 +00:00
"math"
2022-02-06 00:24:51 +00:00
"net/http"
2023-06-05 23:12:10 +00:00
"os"
2024-03-11 16:37:15 +00:00
"slices"
2022-06-14 13:46:33 +00:00
"strings"
2022-01-25 19:52:58 +00:00
"testing"
2022-04-07 09:03:35 +00:00
"time"
2022-01-25 19:52:58 +00:00
"github.com/google/uuid"
2022-09-10 16:07:45 +00:00
"github.com/stretchr/testify/assert"
2022-01-25 19:52:58 +00:00
"github.com/stretchr/testify/require"
2022-11-07 15:25:18 +00:00
"cdr.dev/slog"
"cdr.dev/slog/sloggers/slogtest"
2023-09-26 11:05:19 +00:00
"github.com/coder/coder/v2/agent/agenttest"
2023-08-18 18:55:43 +00:00
"github.com/coder/coder/v2/coderd/audit"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbauthz"
2023-12-01 01:33:04 +00:00
"github.com/coder/coder/v2/coderd/database/dbfake"
2023-08-18 18:55:43 +00:00
"github.com/coder/coder/v2/coderd/database/dbgen"
"github.com/coder/coder/v2/coderd/database/dbtestutil"
2023-09-01 16:50:12 +00:00
"github.com/coder/coder/v2/coderd/database/dbtime"
2023-08-18 18:55:43 +00:00
"github.com/coder/coder/v2/coderd/parameter"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/coderd/schedule"
2023-09-04 13:48:25 +00:00
"github.com/coder/coder/v2/coderd/schedule/cron"
2023-08-18 18:55:43 +00:00
"github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/cryptorand"
"github.com/coder/coder/v2/provisioner/echo"
"github.com/coder/coder/v2/provisionersdk/proto"
"github.com/coder/coder/v2/testutil"
2022-01-25 19:52:58 +00:00
)
2022-05-19 14:29:10 +00:00
func TestWorkspace ( t * testing . T ) {
t . Parallel ( )
t . Run ( "OK" , func ( t * testing . T ) {
t . Parallel ( )
2023-03-21 14:10:22 +00:00
client , _ , api := coderdtest . NewWithAPI ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
2022-05-19 14:29:10 +00:00
user := coderdtest . CreateFirstUser ( t , client )
2023-03-21 14:10:22 +00:00
authz := coderdtest . AssertRBAC ( t , api , client )
2022-05-19 14:29:10 +00:00
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-05-19 14:29:10 +00:00
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2023-03-21 14:10:22 +00:00
authz . Reset ( ) // Reset all previous checks done in setup.
2022-08-09 17:17:00 +00:00
ws , err := client . Workspace ( ctx , workspace . ID )
2023-03-21 14:10:22 +00:00
authz . AssertChecked ( t , rbac . ActionRead , ws )
2022-05-19 14:29:10 +00:00
require . NoError ( t , err )
2022-06-17 17:41:11 +00:00
require . Equal ( t , user . UserID , ws . LatestBuild . InitiatorID )
require . Equal ( t , codersdk . BuildReasonInitiator , ws . LatestBuild . Reason )
2022-05-19 14:29:10 +00:00
} )
t . Run ( "Deleted" , func ( t * testing . T ) {
t . Parallel ( )
2022-09-04 16:28:09 +00:00
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
2022-05-19 14:29:10 +00:00
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-05-19 14:29:10 +00:00
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2022-05-19 14:29:10 +00:00
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2022-06-06 17:23:02 +00:00
// Getting with deleted=true should still work.
2022-08-09 17:17:00 +00:00
_ , err := client . DeletedWorkspace ( ctx , workspace . ID )
2022-06-06 17:23:02 +00:00
require . NoError ( t , err )
2022-05-19 14:29:10 +00:00
// Delete the workspace
2022-08-09 17:17:00 +00:00
build , err := client . CreateWorkspaceBuild ( ctx , workspace . ID , codersdk . CreateWorkspaceBuildRequest {
2022-05-19 18:04:44 +00:00
Transition : codersdk . WorkspaceTransitionDelete ,
2022-05-19 14:29:10 +00:00
} )
require . NoError ( t , err , "delete the workspace" )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , build . ID )
2022-05-19 14:29:10 +00:00
// Getting with deleted=true should work.
2022-08-09 17:17:00 +00:00
workspaceNew , err := client . DeletedWorkspace ( ctx , workspace . ID )
2022-05-19 14:29:10 +00:00
require . NoError ( t , err )
require . Equal ( t , workspace . ID , workspaceNew . ID )
// Getting with deleted=false should not work.
2022-08-09 17:17:00 +00:00
_ , err = client . Workspace ( ctx , workspace . ID )
2022-05-19 14:29:10 +00:00
require . Error ( t , err )
require . ErrorContains ( t , err , "410" ) // gone
} )
2022-08-26 09:28:38 +00:00
t . Run ( "Rename" , func ( t * testing . T ) {
t . Parallel ( )
2023-12-15 18:38:47 +00:00
client := coderdtest . New ( t , & coderdtest . Options {
IncludeProvisionerDaemon : true ,
AllowWorkspaceRenames : true ,
} )
2022-08-26 09:28:38 +00:00
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-08-26 09:28:38 +00:00
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
ws1 := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
ws2 := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , ws1 . LatestBuild . ID )
coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , ws2 . LatestBuild . ID )
2022-08-26 09:28:38 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitMedium )
defer cancel ( )
want := ws1 . Name + "-test"
2023-04-20 18:40:36 +00:00
if len ( want ) > 32 {
want = want [ : 32 - 5 ] + "-test"
}
2023-08-29 11:42:42 +00:00
// Sometimes truncated names result in `--test` which is not an allowed name.
want = strings . Replace ( want , "--" , "-" , - 1 )
2022-08-26 09:28:38 +00:00
err := client . UpdateWorkspace ( ctx , ws1 . ID , codersdk . UpdateWorkspaceRequest {
Name : want ,
} )
require . NoError ( t , err , "workspace rename failed" )
ws , err := client . Workspace ( ctx , ws1 . ID )
require . NoError ( t , err )
require . Equal ( t , want , ws . Name , "workspace name not updated" )
err = client . UpdateWorkspace ( ctx , ws1 . ID , codersdk . UpdateWorkspaceRequest {
Name : ws2 . Name ,
} )
require . Error ( t , err , "workspace rename should have failed" )
} )
2022-11-16 14:50:32 +00:00
2023-12-15 18:38:47 +00:00
t . Run ( "RenameDisabled" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , & coderdtest . Options {
IncludeProvisionerDaemon : true ,
AllowWorkspaceRenames : false ,
} )
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
ws1 := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , ws1 . LatestBuild . ID )
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitMedium )
defer cancel ( )
want := "new-name"
err := client . UpdateWorkspace ( ctx , ws1 . ID , codersdk . UpdateWorkspaceRequest {
Name : want ,
} )
require . ErrorContains ( t , err , "Workspace renames are not allowed" )
} )
2022-11-16 14:50:32 +00:00
t . Run ( "TemplateProperties" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-11-16 14:50:32 +00:00
const templateIcon = "/img/icon.svg"
const templateDisplayName = "This is template"
2023-02-19 00:32:09 +00:00
templateAllowUserCancelWorkspaceJobs := false
2022-11-16 14:50:32 +00:00
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID , func ( ctr * codersdk . CreateTemplateRequest ) {
ctr . Icon = templateIcon
ctr . DisplayName = templateDisplayName
2022-11-21 10:43:53 +00:00
ctr . AllowUserCancelWorkspaceJobs = & templateAllowUserCancelWorkspaceJobs
2022-11-16 14:50:32 +00:00
} )
require . NotEmpty ( t , template . Name )
require . NotEmpty ( t , template . DisplayName )
require . NotEmpty ( t , template . Icon )
2022-11-21 10:43:53 +00:00
require . False ( t , template . AllowUserCancelWorkspaceJobs )
2022-11-16 14:50:32 +00:00
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
ws , err := client . Workspace ( ctx , workspace . ID )
require . NoError ( t , err )
assert . Equal ( t , user . UserID , ws . LatestBuild . InitiatorID )
assert . Equal ( t , codersdk . BuildReasonInitiator , ws . LatestBuild . Reason )
assert . Equal ( t , template . Name , ws . TemplateName )
assert . Equal ( t , templateIcon , ws . TemplateIcon )
assert . Equal ( t , templateDisplayName , ws . TemplateDisplayName )
2022-11-21 10:43:53 +00:00
assert . Equal ( t , templateAllowUserCancelWorkspaceJobs , ws . TemplateAllowUserCancelWorkspaceJobs )
2022-11-16 14:50:32 +00:00
} )
2023-07-10 09:40:11 +00:00
t . Run ( "Health" , func ( t * testing . T ) {
t . Parallel ( )
t . Run ( "Healthy" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , & echo . Responses {
Parse : echo . ParseComplete ,
2023-08-25 06:10:15 +00:00
ProvisionApply : [ ] * proto . Response { {
Type : & proto . Response_Apply {
Apply : & proto . ApplyComplete {
2023-07-10 09:40:11 +00:00
Resources : [ ] * proto . Resource { {
Name : "some" ,
Type : "example" ,
Agents : [ ] * proto . Agent { {
Id : uuid . NewString ( ) ,
Auth : & proto . Agent_Token { } ,
} } ,
} } ,
} ,
} ,
} } ,
} )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2023-07-10 09:40:11 +00:00
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2023-07-10 09:40:11 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
workspace , err := client . Workspace ( ctx , workspace . ID )
require . NoError ( t , err )
agent := workspace . LatestBuild . Resources [ 0 ] . Agents [ 0 ]
assert . True ( t , workspace . Health . Healthy )
assert . Equal ( t , [ ] uuid . UUID { } , workspace . Health . FailingAgents )
assert . True ( t , agent . Health . Healthy )
assert . Empty ( t , agent . Health . Reason )
} )
t . Run ( "Unhealthy" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , & echo . Responses {
Parse : echo . ParseComplete ,
2023-08-25 06:10:15 +00:00
ProvisionApply : [ ] * proto . Response { {
Type : & proto . Response_Apply {
Apply : & proto . ApplyComplete {
2023-07-10 09:40:11 +00:00
Resources : [ ] * proto . Resource { {
Name : "some" ,
Type : "example" ,
Agents : [ ] * proto . Agent { {
Id : uuid . NewString ( ) ,
Auth : & proto . Agent_Token { } ,
ConnectionTimeoutSeconds : 1 ,
} } ,
} } ,
} ,
} ,
} } ,
} )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2023-07-10 09:40:11 +00:00
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2023-07-10 09:40:11 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
var err error
testutil . Eventually ( ctx , t , func ( ctx context . Context ) bool {
workspace , err = client . Workspace ( ctx , workspace . ID )
return assert . NoError ( t , err ) && ! workspace . Health . Healthy
} , testutil . IntervalMedium )
agent := workspace . LatestBuild . Resources [ 0 ] . Agents [ 0 ]
assert . False ( t , workspace . Health . Healthy )
assert . Equal ( t , [ ] uuid . UUID { agent . ID } , workspace . Health . FailingAgents )
assert . False ( t , agent . Health . Healthy )
assert . NotEmpty ( t , agent . Health . Reason )
} )
t . Run ( "Mixed health" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , & echo . Responses {
Parse : echo . ParseComplete ,
2023-08-25 06:10:15 +00:00
ProvisionApply : [ ] * proto . Response { {
Type : & proto . Response_Apply {
Apply : & proto . ApplyComplete {
2023-07-10 09:40:11 +00:00
Resources : [ ] * proto . Resource { {
Name : "some" ,
Type : "example" ,
Agents : [ ] * proto . Agent { {
Id : uuid . NewString ( ) ,
Name : "a1" ,
Auth : & proto . Agent_Token { } ,
} , {
Id : uuid . NewString ( ) ,
Name : "a2" ,
Auth : & proto . Agent_Token { } ,
ConnectionTimeoutSeconds : 1 ,
} } ,
} } ,
} ,
} ,
} } ,
} )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2023-07-10 09:40:11 +00:00
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2023-07-10 09:40:11 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
var err error
testutil . Eventually ( ctx , t , func ( ctx context . Context ) bool {
workspace , err = client . Workspace ( ctx , workspace . ID )
return assert . NoError ( t , err ) && ! workspace . Health . Healthy
} , testutil . IntervalMedium )
assert . False ( t , workspace . Health . Healthy )
assert . Len ( t , workspace . Health . FailingAgents , 1 )
agent1 := workspace . LatestBuild . Resources [ 0 ] . Agents [ 0 ]
agent2 := workspace . LatestBuild . Resources [ 0 ] . Agents [ 1 ]
assert . Equal ( t , [ ] uuid . UUID { agent2 . ID } , workspace . Health . FailingAgents )
assert . True ( t , agent1 . Health . Healthy )
assert . Empty ( t , agent1 . Health . Reason )
assert . False ( t , agent2 . Health . Healthy )
assert . NotEmpty ( t , agent2 . Health . Reason )
} )
} )
2023-10-11 14:26:22 +00:00
t . Run ( "Archived" , func ( t * testing . T ) {
t . Parallel ( )
ownerClient := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
owner := coderdtest . CreateFirstUser ( t , ownerClient )
client , _ := coderdtest . CreateAnotherUser ( t , ownerClient , owner . OrganizationID , rbac . RoleTemplateAdmin ( ) )
active := coderdtest . CreateTemplateVersion ( t , client , owner . OrganizationID , nil )
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , active . ID )
template := coderdtest . CreateTemplate ( t , client , owner . OrganizationID , active . ID )
// We need another version because the active template version cannot be
// archived.
version := coderdtest . CreateTemplateVersion ( t , client , owner . OrganizationID , nil , func ( request * codersdk . CreateTemplateVersionRequest ) {
request . TemplateID = template . ID
} )
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
ctx := testutil . Context ( t , testutil . WaitMedium )
err := client . SetArchiveTemplateVersion ( ctx , version . ID , true )
require . NoError ( t , err , "archive version" )
_ , err = client . CreateWorkspace ( ctx , owner . OrganizationID , codersdk . Me , codersdk . CreateWorkspaceRequest {
TemplateVersionID : version . ID ,
Name : "testworkspace" ,
} )
require . Error ( t , err , "create workspace with archived version" )
require . ErrorContains ( t , err , "Archived template versions cannot" )
} )
2022-05-19 14:29:10 +00:00
}
2023-11-09 05:24:56 +00:00
func TestResolveAutostart ( t * testing . T ) {
t . Parallel ( )
2023-12-01 01:33:04 +00:00
ownerClient , db := coderdtest . NewWithDatabase ( t , nil )
owner := coderdtest . CreateFirstUser ( t , ownerClient )
param := database . TemplateVersionParameter {
Name : "param" ,
DefaultValue : "" ,
Required : true ,
}
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
client , member := coderdtest . CreateAnotherUser ( t , ownerClient , owner . OrganizationID )
resp := dbfake . WorkspaceBuild ( t , db , database . Workspace {
OwnerID : member . ID ,
OrganizationID : owner . OrganizationID ,
AutomaticUpdates : database . AutomaticUpdatesAlways ,
} ) . Seed ( database . WorkspaceBuild {
InitiatorID : member . ID ,
} ) . Do ( )
workspace := resp . Workspace
version1 := resp . TemplateVersion
version2 := dbfake . TemplateVersion ( t , db ) .
Seed ( database . TemplateVersion {
CreatedBy : owner . UserID ,
OrganizationID : owner . OrganizationID ,
TemplateID : version1 . TemplateID ,
} ) .
Params ( param ) . Do ( )
// Autostart shouldn't be possible if parameters do not match.
resolveResp , err := client . ResolveAutostart ( ctx , workspace . ID . String ( ) )
require . NoError ( t , err )
require . True ( t , resolveResp . ParameterMismatch )
_ = dbfake . WorkspaceBuild ( t , db , workspace ) .
Seed ( database . WorkspaceBuild {
BuildNumber : 2 ,
TemplateVersionID : version2 . TemplateVersion . ID ,
} ) .
Params ( database . WorkspaceBuildParameter {
Name : "param" ,
Value : "hello" ,
} ) . Do ( )
// We should be able to autostart since parameters are updated.
resolveResp , err = client . ResolveAutostart ( ctx , workspace . ID . String ( ) )
require . NoError ( t , err )
require . False ( t , resolveResp . ParameterMismatch )
// Create another version that has the same parameters as version2.
// We should be able to update without issue.
_ = dbfake . TemplateVersion ( t , db ) . Seed ( database . TemplateVersion {
CreatedBy : owner . UserID ,
OrganizationID : owner . OrganizationID ,
TemplateID : version1 . TemplateID ,
} ) . Params ( param ) . Do ( )
// Even though we're out of date we should still be able to autostart
// since parameters resolve.
resolveResp , err = client . ResolveAutostart ( ctx , workspace . ID . String ( ) )
require . NoError ( t , err )
require . False ( t , resolveResp . ParameterMismatch )
2023-11-09 05:24:56 +00:00
}
2022-05-18 23:15:19 +00:00
func TestAdminViewAllWorkspaces ( t * testing . T ) {
2022-01-25 19:52:58 +00:00
t . Parallel ( )
2022-09-04 16:28:09 +00:00
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
2022-03-07 17:40:54 +00:00
user := coderdtest . CreateFirstUser ( t , client )
2022-04-06 17:42:40 +00:00
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-04-06 17:42:40 +00:00
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
2022-04-25 21:11:03 +00:00
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
_ , err := client . Workspace ( ctx , workspace . ID )
2022-03-07 17:40:54 +00:00
require . NoError ( t , err )
2022-05-18 23:15:19 +00:00
2022-08-09 17:17:00 +00:00
otherOrg , err := client . CreateOrganization ( ctx , codersdk . CreateOrganizationRequest {
2022-05-18 23:15:19 +00:00
Name : "default-test" ,
} )
require . NoError ( t , err , "create other org" )
// This other user is not in the first user's org. Since other is an admin, they can
// still see the "first" user's workspace.
2023-02-06 23:48:21 +00:00
otherOwner , _ := coderdtest . CreateAnotherUser ( t , client , otherOrg . ID , rbac . RoleOwner ( ) )
2022-11-16 17:01:09 +00:00
otherWorkspaces , err := otherOwner . Workspaces ( ctx , codersdk . WorkspaceFilter { } )
2022-05-18 23:15:19 +00:00
require . NoError ( t , err , "(other) fetch workspaces" )
2022-11-16 17:01:09 +00:00
firstWorkspaces , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter { } )
2022-05-18 23:15:19 +00:00
require . NoError ( t , err , "(first) fetch workspaces" )
2022-11-10 18:25:46 +00:00
require . ElementsMatch ( t , otherWorkspaces . Workspaces , firstWorkspaces . Workspaces )
2022-11-16 17:01:09 +00:00
require . Equal ( t , len ( firstWorkspaces . Workspaces ) , 1 , "should be 1 workspace present" )
2023-02-06 23:48:21 +00:00
memberView , _ := coderdtest . CreateAnotherUser ( t , client , otherOrg . ID )
2022-11-16 17:01:09 +00:00
memberViewWorkspaces , err := memberView . Workspaces ( ctx , codersdk . WorkspaceFilter { } )
require . NoError ( t , err , "(member) fetch workspaces" )
require . Equal ( t , 0 , len ( memberViewWorkspaces . Workspaces ) , "member in other org should see 0 workspaces" )
2022-02-06 00:24:51 +00:00
}
2022-01-25 19:52:58 +00:00
2023-05-25 16:35:47 +00:00
func TestWorkspacesSortOrder ( t * testing . T ) {
t . Parallel ( )
2024-01-24 13:39:19 +00:00
client , db := coderdtest . NewWithDatabase ( t , nil )
2023-05-25 16:35:47 +00:00
firstUser := coderdtest . CreateFirstUser ( t , client )
2024-01-24 13:39:19 +00:00
secondUserClient , secondUser := coderdtest . CreateAnotherUserMutators ( t , client , firstUser . OrganizationID , [ ] string { "owner" } , func ( r * codersdk . CreateUserRequest ) {
r . Username = "zzz"
} )
2023-05-25 16:35:47 +00:00
// c-workspace should be running
2024-01-24 13:39:19 +00:00
wsbC := dbfake . WorkspaceBuild ( t , db , database . Workspace { Name : "c-workspace" , OwnerID : firstUser . UserID , OrganizationID : firstUser . OrganizationID } ) . Do ( )
2023-05-25 16:35:47 +00:00
// b-workspace should be stopped
2024-01-24 13:39:19 +00:00
wsbB := dbfake . WorkspaceBuild ( t , db , database . Workspace { Name : "b-workspace" , OwnerID : firstUser . UserID , OrganizationID : firstUser . OrganizationID } ) . Seed ( database . WorkspaceBuild { Transition : database . WorkspaceTransitionStop } ) . Do ( )
2023-05-25 16:35:47 +00:00
// a-workspace should be running
2024-01-24 13:39:19 +00:00
wsbA := dbfake . WorkspaceBuild ( t , db , database . Workspace { Name : "a-workspace" , OwnerID : firstUser . UserID , OrganizationID : firstUser . OrganizationID } ) . Do ( )
// d-workspace should be stopped
wsbD := dbfake . WorkspaceBuild ( t , db , database . Workspace { Name : "d-workspace" , OwnerID : secondUser . ID , OrganizationID : firstUser . OrganizationID } ) . Seed ( database . WorkspaceBuild { Transition : database . WorkspaceTransitionStop } ) . Do ( )
// e-workspace should also be stopped
wsbE := dbfake . WorkspaceBuild ( t , db , database . Workspace { Name : "e-workspace" , OwnerID : secondUser . ID , OrganizationID : firstUser . OrganizationID } ) . Seed ( database . WorkspaceBuild { Transition : database . WorkspaceTransitionStop } ) . Do ( )
// f-workspace is also stopped, but is marked as favorite
wsbF := dbfake . WorkspaceBuild ( t , db , database . Workspace { Name : "f-workspace" , OwnerID : firstUser . UserID , OrganizationID : firstUser . OrganizationID } ) . Seed ( database . WorkspaceBuild { Transition : database . WorkspaceTransitionStop } ) . Do ( )
2023-05-25 16:35:47 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2024-01-24 13:39:19 +00:00
require . NoError ( t , client . FavoriteWorkspace ( ctx , wsbF . Workspace . ID ) ) // need to do this via API call for now
2023-05-25 16:35:47 +00:00
workspacesResponse , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter { } )
require . NoError ( t , err , "(first) fetch workspaces" )
workspaces := workspacesResponse . Workspaces
2024-01-24 13:39:19 +00:00
expectedNames := [ ] string {
wsbF . Workspace . Name , // favorite
wsbA . Workspace . Name , // running
wsbC . Workspace . Name , // running
wsbB . Workspace . Name , // stopped, testuser < zzz
wsbD . Workspace . Name , // stopped, zzz > testuser
wsbE . Workspace . Name , // stopped, zzz > testuser
2023-05-25 16:35:47 +00:00
}
2024-01-24 13:39:19 +00:00
actualNames := make ( [ ] string , 0 , len ( expectedNames ) )
2023-05-25 16:35:47 +00:00
for _ , w := range workspaces {
2024-01-24 13:39:19 +00:00
actualNames = append ( actualNames , w . Name )
2023-05-25 16:35:47 +00:00
}
// the correct sorting order is:
2024-01-24 13:39:19 +00:00
// 1. Favorite workspaces (we have one, workspace-f)
// 2. Running workspaces
// 3. Sort by usernames
// 4. Sort by workspace names
assert . Equal ( t , expectedNames , actualNames )
// Once again but this time as a different user. This time we do not expect to see another
// user's favorites first.
workspacesResponse , err = secondUserClient . Workspaces ( ctx , codersdk . WorkspaceFilter { } )
require . NoError ( t , err , "(second) fetch workspaces" )
workspaces = workspacesResponse . Workspaces
expectedNames = [ ] string {
wsbA . Workspace . Name , // running
wsbC . Workspace . Name , // running
wsbB . Workspace . Name , // stopped, testuser < zzz
wsbF . Workspace . Name , // stopped, testuser < zzz
wsbD . Workspace . Name , // stopped, zzz > testuser
wsbE . Workspace . Name , // stopped, zzz > testuser
}
actualNames = make ( [ ] string , 0 , len ( expectedNames ) )
for _ , w := range workspaces {
actualNames = append ( actualNames , w . Name )
}
// the correct sorting order is:
// 1. Favorite workspaces (we have none this time)
// 2. Running workspaces
// 3. Sort by usernames
// 4. Sort by workspace names
assert . Equal ( t , expectedNames , actualNames )
2023-05-25 16:35:47 +00:00
}
2022-05-16 19:36:27 +00:00
func TestPostWorkspacesByOrganization ( t * testing . T ) {
2022-03-22 19:17:50 +00:00
t . Parallel ( )
2022-05-16 19:36:27 +00:00
t . Run ( "InvalidTemplate" , func ( t * testing . T ) {
2022-03-22 19:17:50 +00:00
t . Parallel ( )
client := coderdtest . New ( t , nil )
user := coderdtest . CreateFirstUser ( t , client )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2022-09-24 01:17:10 +00:00
_ , err := client . CreateWorkspace ( ctx , user . OrganizationID , codersdk . Me , codersdk . CreateWorkspaceRequest {
2022-05-16 19:36:27 +00:00
TemplateID : uuid . New ( ) ,
Name : "workspace" ,
} )
require . Error ( t , err )
var apiErr * codersdk . Error
require . ErrorAs ( t , err , & apiErr )
require . Equal ( t , http . StatusBadRequest , apiErr . StatusCode ( ) )
} )
t . Run ( "NoTemplateAccess" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , nil )
first := coderdtest . CreateFirstUser ( t , client )
2023-02-06 23:48:21 +00:00
other , _ := coderdtest . CreateAnotherUser ( t , client , first . OrganizationID , rbac . RoleMember ( ) , rbac . RoleOwner ( ) )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
org , err := other . CreateOrganization ( ctx , codersdk . CreateOrganizationRequest {
2022-05-16 19:36:27 +00:00
Name : "another" ,
} )
require . NoError ( t , err )
version := coderdtest . CreateTemplateVersion ( t , other , org . ID , nil )
template := coderdtest . CreateTemplate ( t , other , org . ID , version . ID )
2022-09-24 01:17:10 +00:00
_ , err = client . CreateWorkspace ( ctx , first . OrganizationID , codersdk . Me , codersdk . CreateWorkspaceRequest {
2022-05-16 19:36:27 +00:00
TemplateID : template . ID ,
Name : "workspace" ,
} )
require . Error ( t , err )
var apiErr * codersdk . Error
require . ErrorAs ( t , err , & apiErr )
2023-08-30 21:14:24 +00:00
require . Equal ( t , http . StatusForbidden , apiErr . StatusCode ( ) )
2022-05-16 19:36:27 +00:00
} )
t . Run ( "AlreadyExists" , func ( t * testing . T ) {
t . Parallel ( )
2022-09-04 16:28:09 +00:00
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
2022-05-16 19:36:27 +00:00
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-05-16 19:36:27 +00:00
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2022-09-24 01:17:10 +00:00
_ , err := client . CreateWorkspace ( ctx , user . OrganizationID , codersdk . Me , codersdk . CreateWorkspaceRequest {
2022-05-16 19:36:27 +00:00
TemplateID : template . ID ,
Name : workspace . Name ,
} )
require . Error ( t , err )
var apiErr * codersdk . Error
require . ErrorAs ( t , err , & apiErr )
require . Equal ( t , http . StatusConflict , apiErr . StatusCode ( ) )
} )
2023-09-29 13:06:21 +00:00
t . Run ( "CreateWithAuditLogs" , func ( t * testing . T ) {
2022-05-16 19:36:27 +00:00
t . Parallel ( )
2022-09-10 16:07:45 +00:00
auditor := audit . NewMock ( )
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true , Auditor : auditor } )
2022-05-16 19:36:27 +00:00
user := coderdtest . CreateFirstUser ( t , client )
2022-04-06 17:42:40 +00:00
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2023-03-16 17:47:54 +00:00
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2023-11-06 09:17:07 +00:00
assert . True ( t , auditor . Contains ( t , database . AuditLog {
ResourceType : database . ResourceTypeWorkspace ,
Action : database . AuditActionCreate ,
ResourceTarget : workspace . Name ,
} ) )
2022-05-16 19:36:27 +00:00
} )
2022-05-30 19:19:17 +00:00
2023-09-29 13:06:21 +00:00
t . Run ( "CreateFromVersionWithAuditLogs" , func ( t * testing . T ) {
2023-08-31 21:07:58 +00:00
t . Parallel ( )
auditor := audit . NewMock ( )
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true , Auditor : auditor } )
user := coderdtest . CreateFirstUser ( t , client )
versionDefault := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , versionDefault . ID )
versionTest := coderdtest . UpdateTemplateVersion ( t , client , user . OrganizationID , nil , template . ID )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , versionDefault . ID )
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , versionTest . ID )
2023-08-31 21:07:58 +00:00
defaultWorkspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , uuid . Nil ,
func ( c * codersdk . CreateWorkspaceRequest ) { c . TemplateVersionID = versionDefault . ID } ,
)
testWorkspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , uuid . Nil ,
func ( c * codersdk . CreateWorkspaceRequest ) { c . TemplateVersionID = versionTest . ID } ,
)
2023-10-03 17:02:56 +00:00
defaultWorkspaceBuild := coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , defaultWorkspace . LatestBuild . ID )
testWorkspaceBuild := coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , testWorkspace . LatestBuild . ID )
2023-08-31 21:07:58 +00:00
require . Equal ( t , testWorkspaceBuild . TemplateVersionID , versionTest . ID )
require . Equal ( t , defaultWorkspaceBuild . TemplateVersionID , versionDefault . ID )
2023-11-06 09:17:07 +00:00
assert . True ( t , auditor . Contains ( t , database . AuditLog {
ResourceType : database . ResourceTypeWorkspace ,
Action : database . AuditActionCreate ,
ResourceTarget : defaultWorkspace . Name ,
} ) )
2023-08-31 21:07:58 +00:00
} )
t . Run ( "InvalidCombinationOfTemplateAndTemplateVersion" , func ( t * testing . T ) {
t . Parallel ( )
auditor := audit . NewMock ( )
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true , Auditor : auditor } )
user := coderdtest . CreateFirstUser ( t , client )
versionTest := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
versionDefault := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , versionDefault . ID )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , versionTest . ID )
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , versionDefault . ID )
2023-08-31 21:07:58 +00:00
name , se := cryptorand . String ( 8 )
require . NoError ( t , se )
req := codersdk . CreateWorkspaceRequest {
// Deny setting both of these ID fields, even if they might correlate.
// Allowing both to be set would just create extra work for everyone involved.
TemplateID : template . ID ,
TemplateVersionID : versionTest . ID ,
Name : name ,
AutostartSchedule : ptr . Ref ( "CRON_TZ=US/Central 30 9 * * 1-5" ) ,
TTLMillis : ptr . Ref ( ( 8 * time . Hour ) . Milliseconds ( ) ) ,
}
_ , err := client . CreateWorkspace ( context . Background ( ) , user . OrganizationID , codersdk . Me , req )
require . Error ( t , err )
} )
2022-11-10 22:53:14 +00:00
t . Run ( "CreateWithDeletedTemplate" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-11-10 22:53:14 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
err := client . DeleteTemplate ( ctx , template . ID )
require . NoError ( t , err )
_ , err = client . CreateWorkspace ( ctx , user . OrganizationID , codersdk . Me , codersdk . CreateWorkspaceRequest {
TemplateID : template . ID ,
Name : "testing" ,
} )
require . Error ( t , err )
var apiErr * codersdk . Error
require . ErrorAs ( t , err , & apiErr )
require . Equal ( t , http . StatusNotFound , apiErr . StatusCode ( ) )
} )
2022-08-24 14:45:14 +00:00
t . Run ( "TemplateNoTTL" , func ( t * testing . T ) {
t . Parallel ( )
2022-09-04 16:28:09 +00:00
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
2022-08-24 14:45:14 +00:00
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID , func ( ctr * codersdk . CreateTemplateRequest ) {
2022-11-09 19:36:25 +00:00
ctr . DefaultTTLMillis = ptr . Ref ( int64 ( 0 ) )
2022-08-24 14:45:14 +00:00
} )
2022-11-23 15:30:38 +00:00
// Given: the template has no default TTL set
2022-11-09 19:36:25 +00:00
require . Zero ( t , template . DefaultTTLMillis )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-08-24 14:45:14 +00:00
// When: we create a workspace with autostop not enabled
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID , func ( cwr * codersdk . CreateWorkspaceRequest ) {
cwr . TTLMillis = ptr . Ref ( int64 ( 0 ) )
} )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2023-09-14 09:20:09 +00:00
2022-08-24 14:45:14 +00:00
// Then: No TTL should be set by the template
require . Nil ( t , workspace . TTLMillis )
} )
2022-06-07 12:37:45 +00:00
t . Run ( "TemplateCustomTTL" , func ( t * testing . T ) {
t . Parallel ( )
2022-09-04 16:28:09 +00:00
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
2022-06-07 12:37:45 +00:00
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
templateTTL := 24 * time . Hour . Milliseconds ( )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID , func ( ctr * codersdk . CreateTemplateRequest ) {
2022-11-09 19:36:25 +00:00
ctr . DefaultTTLMillis = ptr . Ref ( templateTTL )
2022-06-07 12:37:45 +00:00
} )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-06-07 12:37:45 +00:00
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID , func ( cwr * codersdk . CreateWorkspaceRequest ) {
cwr . TTLMillis = nil // ensure that no default TTL is set
} )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2023-09-14 09:20:09 +00:00
2022-06-07 12:37:45 +00:00
// TTL should be set by the template
2024-04-11 19:08:51 +00:00
require . Equal ( t , templateTTL , template . DefaultTTLMillis )
require . Equal ( t , templateTTL , * workspace . TTLMillis )
2022-06-07 12:37:45 +00:00
} )
2022-05-30 19:19:17 +00:00
t . Run ( "InvalidTTL" , func ( t * testing . T ) {
t . Parallel ( )
t . Run ( "BelowMin" , func ( t * testing . T ) {
t . Parallel ( )
2022-09-04 16:28:09 +00:00
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
2022-05-30 19:19:17 +00:00
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2022-05-30 19:19:17 +00:00
req := codersdk . CreateWorkspaceRequest {
2022-06-07 12:37:45 +00:00
TemplateID : template . ID ,
Name : "testing" ,
TTLMillis : ptr . Ref ( ( 59 * time . Second ) . Milliseconds ( ) ) ,
2022-05-30 19:19:17 +00:00
}
2022-09-24 01:17:10 +00:00
_ , err := client . CreateWorkspace ( ctx , template . OrganizationID , codersdk . Me , req )
2022-05-30 19:19:17 +00:00
require . Error ( t , err )
var apiErr * codersdk . Error
require . ErrorAs ( t , err , & apiErr )
require . Equal ( t , http . StatusBadRequest , apiErr . StatusCode ( ) )
2022-06-07 12:37:45 +00:00
require . Len ( t , apiErr . Validations , 1 )
2024-04-11 19:08:51 +00:00
require . Equal ( t , "ttl_ms" , apiErr . Validations [ 0 ] . Field )
2022-07-27 21:20:02 +00:00
require . Equal ( t , "time until shutdown must be at least one minute" , apiErr . Validations [ 0 ] . Detail )
2022-05-30 19:19:17 +00:00
} )
} )
2022-06-07 12:37:45 +00:00
2022-11-09 19:36:25 +00:00
t . Run ( "TemplateDefaultTTL" , func ( t * testing . T ) {
2022-06-07 12:37:45 +00:00
t . Parallel ( )
2022-09-04 16:28:09 +00:00
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
2022-06-07 12:37:45 +00:00
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
2022-11-09 19:36:25 +00:00
exp := 24 * time . Hour . Milliseconds ( )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID , func ( ctr * codersdk . CreateTemplateRequest ) {
ctr . DefaultTTLMillis = & exp
} )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2022-11-09 19:36:25 +00:00
// no TTL provided should use template default
2022-06-07 12:37:45 +00:00
req := codersdk . CreateWorkspaceRequest {
2022-11-09 19:36:25 +00:00
TemplateID : template . ID ,
Name : "testing" ,
2022-06-07 12:37:45 +00:00
}
2022-11-09 19:36:25 +00:00
ws , err := client . CreateWorkspace ( ctx , template . OrganizationID , codersdk . Me , req )
require . NoError ( t , err )
require . EqualValues ( t , exp , * ws . TTLMillis )
// TTL provided should override template default
req . Name = "testing2"
exp = 1 * time . Hour . Milliseconds ( )
req . TTLMillis = & exp
ws , err = client . CreateWorkspace ( ctx , template . OrganizationID , codersdk . Me , req )
require . NoError ( t , err )
require . EqualValues ( t , exp , * ws . TTLMillis )
2022-06-07 12:37:45 +00:00
} )
2022-05-16 19:36:27 +00:00
}
func TestWorkspaceByOwnerAndName ( t * testing . T ) {
t . Parallel ( )
t . Run ( "NotFound" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , nil )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
_ , err := client . WorkspaceByOwnerAndName ( ctx , codersdk . Me , "something" , codersdk . WorkspaceOptions { } )
2022-05-16 19:36:27 +00:00
var apiErr * codersdk . Error
require . ErrorAs ( t , err , & apiErr )
2022-06-03 19:36:08 +00:00
require . Equal ( t , http . StatusUnauthorized , apiErr . StatusCode ( ) )
2022-05-16 19:36:27 +00:00
} )
t . Run ( "Get" , func ( t * testing . T ) {
t . Parallel ( )
2022-09-04 16:28:09 +00:00
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
2022-05-16 19:36:27 +00:00
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-05-16 19:36:27 +00:00
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
2022-04-25 21:11:03 +00:00
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
_ , err := client . WorkspaceByOwnerAndName ( ctx , codersdk . Me , workspace . Name , codersdk . WorkspaceOptions { } )
2022-03-22 19:17:50 +00:00
require . NoError ( t , err )
} )
2022-06-08 18:04:05 +00:00
t . Run ( "Deleted" , func ( t * testing . T ) {
t . Parallel ( )
2022-09-04 16:28:09 +00:00
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
2022-06-08 18:04:05 +00:00
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-06-08 18:04:05 +00:00
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2022-06-08 18:04:05 +00:00
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2022-06-08 18:04:05 +00:00
// Given:
// We delete the workspace
2022-08-09 17:17:00 +00:00
build , err := client . CreateWorkspaceBuild ( ctx , workspace . ID , codersdk . CreateWorkspaceBuildRequest {
2022-06-08 18:04:05 +00:00
Transition : codersdk . WorkspaceTransitionDelete ,
} )
require . NoError ( t , err , "delete the workspace" )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , build . ID )
2022-06-08 18:04:05 +00:00
// Then:
// When we call without includes_deleted, we don't expect to get the workspace back
2022-08-09 17:17:00 +00:00
_ , err = client . WorkspaceByOwnerAndName ( ctx , workspace . OwnerName , workspace . Name , codersdk . WorkspaceOptions { } )
2022-06-14 15:14:05 +00:00
require . ErrorContains ( t , err , "404" )
2022-06-08 18:04:05 +00:00
// Then:
// When we call with includes_deleted, we should get the workspace back
2022-08-09 17:17:00 +00:00
workspaceNew , err := client . WorkspaceByOwnerAndName ( ctx , workspace . OwnerName , workspace . Name , codersdk . WorkspaceOptions { IncludeDeleted : true } )
2022-06-10 14:58:42 +00:00
require . NoError ( t , err )
require . Equal ( t , workspace . ID , workspaceNew . ID )
// Given:
// We recreate the workspace with the same name
2022-09-24 01:17:10 +00:00
workspace , err = client . CreateWorkspace ( ctx , user . OrganizationID , codersdk . Me , codersdk . CreateWorkspaceRequest {
2022-06-10 14:58:42 +00:00
TemplateID : workspace . TemplateID ,
Name : workspace . Name ,
AutostartSchedule : workspace . AutostartSchedule ,
TTLMillis : workspace . TTLMillis ,
2023-10-06 09:27:12 +00:00
AutomaticUpdates : workspace . AutomaticUpdates ,
2022-06-10 14:58:42 +00:00
} )
require . NoError ( t , err )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2022-06-10 14:58:42 +00:00
// Then:
// We can fetch the most recent workspace
2022-08-09 17:17:00 +00:00
workspaceNew , err = client . WorkspaceByOwnerAndName ( ctx , workspace . OwnerName , workspace . Name , codersdk . WorkspaceOptions { } )
2022-06-10 14:58:42 +00:00
require . NoError ( t , err )
require . Equal ( t , workspace . ID , workspaceNew . ID )
// Given:
// We delete the workspace again
2022-08-09 17:17:00 +00:00
build , err = client . CreateWorkspaceBuild ( ctx , workspace . ID , codersdk . CreateWorkspaceBuildRequest {
2022-06-10 14:58:42 +00:00
Transition : codersdk . WorkspaceTransitionDelete ,
} )
require . NoError ( t , err , "delete the workspace" )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , build . ID )
2022-06-10 14:58:42 +00:00
// Then:
// When we fetch the deleted workspace, we get the most recently deleted one
2022-08-09 17:17:00 +00:00
workspaceNew , err = client . WorkspaceByOwnerAndName ( ctx , workspace . OwnerName , workspace . Name , codersdk . WorkspaceOptions { IncludeDeleted : true } )
2022-06-08 18:04:05 +00:00
require . NoError ( t , err )
require . Equal ( t , workspace . ID , workspaceNew . ID )
} )
2022-03-22 19:17:50 +00:00
}
2023-06-05 23:12:10 +00:00
// TestWorkspaceFilterAllStatus tests workspace status is correctly set given a set of conditions.
func TestWorkspaceFilterAllStatus ( t * testing . T ) {
t . Parallel ( )
if os . Getenv ( "DB" ) != "" {
t . Skip ( ` This test takes too long with an actual database. Takes 10s on local machine ` )
}
// For this test, we do not care about permissions.
// nolint:gocritic // unit testing
ctx := dbauthz . AsSystemRestricted ( context . Background ( ) )
db , pubsub := dbtestutil . NewDB ( t )
client := coderdtest . New ( t , & coderdtest . Options {
Database : db ,
Pubsub : pubsub ,
} )
owner := coderdtest . CreateFirstUser ( t , client )
file := dbgen . File ( t , db , database . File {
CreatedBy : owner . UserID ,
} )
2023-09-19 06:25:57 +00:00
versionJob := dbgen . ProvisionerJob ( t , db , pubsub , database . ProvisionerJob {
2023-06-05 23:12:10 +00:00
OrganizationID : owner . OrganizationID ,
InitiatorID : owner . UserID ,
WorkerID : uuid . NullUUID { } ,
FileID : file . ID ,
2023-06-13 19:14:55 +00:00
Tags : database . StringMap {
2023-06-05 23:12:10 +00:00
"custom" : "true" ,
} ,
} )
version := dbgen . TemplateVersion ( t , db , database . TemplateVersion {
OrganizationID : owner . OrganizationID ,
JobID : versionJob . ID ,
CreatedBy : owner . UserID ,
} )
template := dbgen . Template ( t , db , database . Template {
OrganizationID : owner . OrganizationID ,
ActiveVersionID : version . ID ,
CreatedBy : owner . UserID ,
} )
makeWorkspace := func ( workspace database . Workspace , job database . ProvisionerJob , transition database . WorkspaceTransition ) ( database . Workspace , database . WorkspaceBuild , database . ProvisionerJob ) {
db := db
workspace . OwnerID = owner . UserID
workspace . OrganizationID = owner . OrganizationID
workspace . TemplateID = template . ID
workspace = dbgen . Workspace ( t , db , workspace )
jobID := uuid . New ( )
job . ID = jobID
job . Type = database . ProvisionerJobTypeWorkspaceBuild
job . OrganizationID = owner . OrganizationID
// Need to prevent acquire from getting this job.
2023-06-13 19:14:55 +00:00
job . Tags = database . StringMap {
2023-06-05 23:12:10 +00:00
jobID . String ( ) : "true" ,
}
2023-09-19 06:25:57 +00:00
job = dbgen . ProvisionerJob ( t , db , pubsub , job )
2023-06-05 23:12:10 +00:00
build := dbgen . WorkspaceBuild ( t , db , database . WorkspaceBuild {
WorkspaceID : workspace . ID ,
TemplateVersionID : version . ID ,
BuildNumber : 1 ,
Transition : transition ,
InitiatorID : owner . UserID ,
JobID : job . ID ,
} )
var err error
job , err = db . GetProvisionerJobByID ( ctx , job . ID )
require . NoError ( t , err )
return workspace , build , job
}
// pending
makeWorkspace ( database . Workspace {
Name : string ( database . WorkspaceStatusPending ) ,
} , database . ProvisionerJob {
StartedAt : sql . NullTime { Valid : false } ,
} , database . WorkspaceTransitionStart )
// starting
makeWorkspace ( database . Workspace {
Name : string ( database . WorkspaceStatusStarting ) ,
} , database . ProvisionerJob {
StartedAt : sql . NullTime { Time : time . Now ( ) . Add ( time . Second * - 2 ) , Valid : true } ,
} , database . WorkspaceTransitionStart )
// running
makeWorkspace ( database . Workspace {
Name : string ( database . WorkspaceStatusRunning ) ,
} , database . ProvisionerJob {
CompletedAt : sql . NullTime { Time : time . Now ( ) , Valid : true } ,
StartedAt : sql . NullTime { Time : time . Now ( ) . Add ( time . Second * - 2 ) , Valid : true } ,
} , database . WorkspaceTransitionStart )
// stopping
makeWorkspace ( database . Workspace {
Name : string ( database . WorkspaceStatusStopping ) ,
} , database . ProvisionerJob {
StartedAt : sql . NullTime { Time : time . Now ( ) . Add ( time . Second * - 2 ) , Valid : true } ,
} , database . WorkspaceTransitionStop )
// stopped
makeWorkspace ( database . Workspace {
Name : string ( database . WorkspaceStatusStopped ) ,
} , database . ProvisionerJob {
StartedAt : sql . NullTime { Time : time . Now ( ) . Add ( time . Second * - 2 ) , Valid : true } ,
CompletedAt : sql . NullTime { Time : time . Now ( ) , Valid : true } ,
} , database . WorkspaceTransitionStop )
// failed -- delete
makeWorkspace ( database . Workspace {
Name : string ( database . WorkspaceStatusFailed ) + "-deleted" ,
} , database . ProvisionerJob {
StartedAt : sql . NullTime { Time : time . Now ( ) . Add ( time . Second * - 2 ) , Valid : true } ,
CompletedAt : sql . NullTime { Time : time . Now ( ) , Valid : true } ,
Error : sql . NullString { String : "Some error" , Valid : true } ,
} , database . WorkspaceTransitionDelete )
// failed -- stop
makeWorkspace ( database . Workspace {
Name : string ( database . WorkspaceStatusFailed ) + "-stopped" ,
} , database . ProvisionerJob {
StartedAt : sql . NullTime { Time : time . Now ( ) . Add ( time . Second * - 2 ) , Valid : true } ,
CompletedAt : sql . NullTime { Time : time . Now ( ) , Valid : true } ,
Error : sql . NullString { String : "Some error" , Valid : true } ,
} , database . WorkspaceTransitionStop )
// canceling
makeWorkspace ( database . Workspace {
Name : string ( database . WorkspaceStatusCanceling ) ,
} , database . ProvisionerJob {
StartedAt : sql . NullTime { Time : time . Now ( ) . Add ( time . Second * - 2 ) , Valid : true } ,
CanceledAt : sql . NullTime { Time : time . Now ( ) , Valid : true } ,
} , database . WorkspaceTransitionStart )
// canceled
makeWorkspace ( database . Workspace {
Name : string ( database . WorkspaceStatusCanceled ) ,
} , database . ProvisionerJob {
StartedAt : sql . NullTime { Time : time . Now ( ) . Add ( time . Second * - 2 ) , Valid : true } ,
CanceledAt : sql . NullTime { Time : time . Now ( ) , Valid : true } ,
CompletedAt : sql . NullTime { Time : time . Now ( ) , Valid : true } ,
} , database . WorkspaceTransitionStart )
// deleting
makeWorkspace ( database . Workspace {
Name : string ( database . WorkspaceStatusDeleting ) ,
} , database . ProvisionerJob {
StartedAt : sql . NullTime { Time : time . Now ( ) . Add ( time . Second * - 2 ) , Valid : true } ,
} , database . WorkspaceTransitionDelete )
// deleted
makeWorkspace ( database . Workspace {
Name : string ( database . WorkspaceStatusDeleted ) ,
} , database . ProvisionerJob {
StartedAt : sql . NullTime { Time : time . Now ( ) . Add ( time . Second * - 2 ) , Valid : true } ,
CompletedAt : sql . NullTime { Time : time . Now ( ) , Valid : true } ,
} , database . WorkspaceTransitionDelete )
apiCtx , cancel := context . WithTimeout ( ctx , testutil . WaitShort )
defer cancel ( )
workspaces , err := client . Workspaces ( apiCtx , codersdk . WorkspaceFilter { } )
require . NoError ( t , err )
// Make sure all workspaces have the correct status
var statuses [ ] codersdk . WorkspaceStatus
for _ , apiWorkspace := range workspaces . Workspaces {
expStatus := strings . Split ( apiWorkspace . Name , "-" )
if ! assert . Equal ( t , expStatus [ 0 ] , string ( apiWorkspace . LatestBuild . Status ) , "workspace has incorrect status" ) {
d , _ := json . Marshal ( apiWorkspace )
var buf bytes . Buffer
_ = json . Indent ( & buf , d , "" , "\t" )
t . Logf ( "Incorrect workspace: %s" , buf . String ( ) )
}
statuses = append ( statuses , apiWorkspace . LatestBuild . Status )
}
// Now test the filter
for _ , status := range statuses {
ctx , cancel := context . WithTimeout ( ctx , testutil . WaitShort )
workspaces , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter {
Status : string ( status ) ,
} )
require . NoErrorf ( t , err , "fetch with status: %s" , status )
for _ , workspace := range workspaces . Workspaces {
assert . Equal ( t , status , workspace . LatestBuild . Status , "expect matching status to filter" )
}
cancel ( )
}
}
2022-06-14 13:46:33 +00:00
// TestWorkspaceFilter creates a set of workspaces, users, and organizations
// to run various filters against for testing.
2022-06-06 19:43:16 +00:00
func TestWorkspaceFilter ( t * testing . T ) {
t . Parallel ( )
2022-07-08 18:01:00 +00:00
// Manual tests still occur below, so this is safe to disable.
t . Skip ( "This test is slow and flaky. See: https://github.com/coder/coder/issues/2854" )
// nolint:unused
2022-06-14 13:46:33 +00:00
type coderUser struct {
* codersdk . Client
User codersdk . User
Org codersdk . Organization
}
2022-09-04 16:28:09 +00:00
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
2022-06-14 13:46:33 +00:00
first := coderdtest . CreateFirstUser ( t , client )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
2022-08-21 22:32:53 +00:00
t . Cleanup ( cancel )
2022-08-09 17:17:00 +00:00
2022-06-14 13:46:33 +00:00
users := make ( [ ] coderUser , 0 )
for i := 0 ; i < 10 ; i ++ {
2023-02-06 23:48:21 +00:00
userClient , user := coderdtest . CreateAnotherUser ( t , client , first . OrganizationID , rbac . RoleOwner ( ) )
2022-06-14 13:46:33 +00:00
2022-06-25 11:22:59 +00:00
if i % 3 == 0 {
2023-02-06 23:48:21 +00:00
var err error
2022-08-09 17:17:00 +00:00
user , err = client . UpdateUserProfile ( ctx , user . ID . String ( ) , codersdk . UpdateUserProfileRequest {
2022-06-25 11:22:59 +00:00
Username : strings . ToUpper ( user . Username ) ,
} )
require . NoError ( t , err , "uppercase username" )
}
2022-08-09 17:17:00 +00:00
org , err := userClient . CreateOrganization ( ctx , codersdk . CreateOrganizationRequest {
2022-06-14 13:46:33 +00:00
Name : user . Username + "-org" ,
} )
require . NoError ( t , err , "create org" )
users = append ( users , coderUser {
Client : userClient ,
User : user ,
Org : org ,
} )
}
type madeWorkspace struct {
Owner codersdk . User
Workspace codersdk . Workspace
Template codersdk . Template
}
availTemplates := make ( [ ] codersdk . Template , 0 )
allWorkspaces := make ( [ ] madeWorkspace , 0 )
2022-06-25 11:22:59 +00:00
upperTemplates := make ( [ ] string , 0 )
2022-06-14 13:46:33 +00:00
// Create some random workspaces
2022-06-25 11:22:59 +00:00
var count int
for i , user := range users {
2022-06-14 13:46:33 +00:00
version := coderdtest . CreateTemplateVersion ( t , client , user . Org . ID , nil )
// Create a template & workspace in the user's org
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-06-25 11:22:59 +00:00
var template codersdk . Template
if i % 3 == 0 {
template = coderdtest . CreateTemplate ( t , client , user . Org . ID , version . ID , func ( request * codersdk . CreateTemplateRequest ) {
request . Name = strings . ToUpper ( request . Name )
} )
upperTemplates = append ( upperTemplates , template . Name )
} else {
template = coderdtest . CreateTemplate ( t , client , user . Org . ID , version . ID )
}
2022-06-14 13:46:33 +00:00
availTemplates = append ( availTemplates , template )
2022-06-25 11:22:59 +00:00
workspace := coderdtest . CreateWorkspace ( t , user . Client , template . OrganizationID , template . ID , func ( request * codersdk . CreateWorkspaceRequest ) {
if count % 3 == 0 {
request . Name = strings . ToUpper ( request . Name )
}
} )
2022-06-14 13:46:33 +00:00
allWorkspaces = append ( allWorkspaces , madeWorkspace {
Workspace : workspace ,
Template : template ,
Owner : user . User ,
} )
// Make a workspace with a random template
idx , _ := cryptorand . Intn ( len ( availTemplates ) )
randTemplate := availTemplates [ idx ]
randWorkspace := coderdtest . CreateWorkspace ( t , user . Client , randTemplate . OrganizationID , randTemplate . ID )
allWorkspaces = append ( allWorkspaces , madeWorkspace {
Workspace : randWorkspace ,
Template : randTemplate ,
Owner : user . User ,
} )
}
// Make sure all workspaces are done. Do it after all are made
for i , w := range allWorkspaces {
2023-10-03 17:02:56 +00:00
latest := coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , w . Workspace . LatestBuild . ID )
2022-06-14 13:46:33 +00:00
allWorkspaces [ i ] . Workspace . LatestBuild = latest
}
// --- Setup done ---
testCases := [ ] struct {
Name string
Filter codersdk . WorkspaceFilter
// If FilterF is true, we include it in the expected results
FilterF func ( f codersdk . WorkspaceFilter , workspace madeWorkspace ) bool
} {
{
Name : "All" ,
Filter : codersdk . WorkspaceFilter { } ,
FilterF : func ( _ codersdk . WorkspaceFilter , _ madeWorkspace ) bool {
return true
} ,
} ,
{
Name : "Owner" ,
Filter : codersdk . WorkspaceFilter {
2022-06-25 11:22:59 +00:00
Owner : strings . ToUpper ( users [ 2 ] . User . Username ) ,
2022-06-14 13:46:33 +00:00
} ,
FilterF : func ( f codersdk . WorkspaceFilter , workspace madeWorkspace ) bool {
2022-06-25 11:22:59 +00:00
return strings . EqualFold ( workspace . Owner . Username , f . Owner )
2022-06-14 13:46:33 +00:00
} ,
} ,
{
Name : "TemplateName" ,
Filter : codersdk . WorkspaceFilter {
2022-06-25 11:22:59 +00:00
Template : strings . ToUpper ( allWorkspaces [ 5 ] . Template . Name ) ,
2022-06-14 13:46:33 +00:00
} ,
FilterF : func ( f codersdk . WorkspaceFilter , workspace madeWorkspace ) bool {
2022-06-25 11:22:59 +00:00
return strings . EqualFold ( workspace . Template . Name , f . Template )
} ,
} ,
{
Name : "UpperTemplateName" ,
Filter : codersdk . WorkspaceFilter {
Template : upperTemplates [ 0 ] ,
} ,
FilterF : func ( f codersdk . WorkspaceFilter , workspace madeWorkspace ) bool {
return strings . EqualFold ( workspace . Template . Name , f . Template )
2022-06-14 13:46:33 +00:00
} ,
} ,
{
Name : "Name" ,
Filter : codersdk . WorkspaceFilter {
// Use a common letter... one has to have this letter in it
Name : "a" ,
} ,
FilterF : func ( f codersdk . WorkspaceFilter , workspace madeWorkspace ) bool {
2022-06-25 11:22:59 +00:00
return strings . ContainsAny ( workspace . Workspace . Name , "Aa" )
2022-06-14 13:46:33 +00:00
} ,
} ,
{
Name : "Q-Owner/Name" ,
Filter : codersdk . WorkspaceFilter {
2022-06-25 11:22:59 +00:00
FilterQuery : allWorkspaces [ 5 ] . Owner . Username + "/" + strings . ToUpper ( allWorkspaces [ 5 ] . Workspace . Name ) ,
2022-06-14 13:46:33 +00:00
} ,
2022-06-25 11:22:59 +00:00
FilterF : func ( f codersdk . WorkspaceFilter , workspace madeWorkspace ) bool {
if strings . EqualFold ( workspace . Owner . Username , allWorkspaces [ 5 ] . Owner . Username ) &&
strings . Contains ( strings . ToLower ( workspace . Workspace . Name ) , strings . ToLower ( allWorkspaces [ 5 ] . Workspace . Name ) ) {
return true
}
return false
2022-06-14 13:46:33 +00:00
} ,
} ,
{
Name : "Many filters" ,
Filter : codersdk . WorkspaceFilter {
Owner : allWorkspaces [ 3 ] . Owner . Username ,
Template : allWorkspaces [ 3 ] . Template . Name ,
Name : allWorkspaces [ 3 ] . Workspace . Name ,
} ,
FilterF : func ( f codersdk . WorkspaceFilter , workspace madeWorkspace ) bool {
2022-06-25 11:22:59 +00:00
if strings . EqualFold ( workspace . Owner . Username , f . Owner ) &&
strings . Contains ( strings . ToLower ( workspace . Workspace . Name ) , strings . ToLower ( f . Name ) ) &&
strings . EqualFold ( workspace . Template . Name , f . Template ) {
return true
}
return false
2022-06-14 13:46:33 +00:00
} ,
} ,
}
for _ , c := range testCases {
c := c
t . Run ( c . Name , func ( t * testing . T ) {
t . Parallel ( )
2022-08-09 17:17:00 +00:00
workspaces , err := client . Workspaces ( ctx , c . Filter )
2022-06-14 13:46:33 +00:00
require . NoError ( t , err , "fetch workspaces" )
exp := make ( [ ] codersdk . Workspace , 0 )
for _ , made := range allWorkspaces {
if c . FilterF ( c . Filter , made ) {
exp = append ( exp , made . Workspace )
}
}
require . ElementsMatch ( t , exp , workspaces , "expected workspaces returned" )
} )
}
}
// TestWorkspaceFilterManual runs some specific setups with basic checks.
func TestWorkspaceFilterManual ( t * testing . T ) {
t . Parallel ( )
2024-03-22 19:22:47 +00:00
expectIDs := func ( t * testing . T , exp [ ] codersdk . Workspace , got [ ] codersdk . Workspace ) {
t . Helper ( )
expIDs := make ( [ ] uuid . UUID , 0 , len ( exp ) )
for _ , e := range exp {
expIDs = append ( expIDs , e . ID )
}
gotIDs := make ( [ ] uuid . UUID , 0 , len ( got ) )
for _ , g := range got {
gotIDs = append ( gotIDs , g . ID )
}
require . ElementsMatchf ( t , expIDs , gotIDs , "expected IDs" )
}
2022-06-06 19:43:16 +00:00
t . Run ( "Name" , func ( t * testing . T ) {
t . Parallel ( )
2022-09-04 16:28:09 +00:00
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
2022-06-06 19:43:16 +00:00
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-06-06 19:43:16 +00:00
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2022-06-06 19:43:16 +00:00
// full match
2022-11-10 18:25:46 +00:00
res , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter {
2022-06-06 19:43:16 +00:00
Name : workspace . Name ,
} )
require . NoError ( t , err )
2022-11-10 18:25:46 +00:00
require . Len ( t , res . Workspaces , 1 , workspace . Name )
require . Equal ( t , workspace . ID , res . Workspaces [ 0 ] . ID )
2022-06-06 19:43:16 +00:00
// partial match
2022-11-10 18:25:46 +00:00
res , err = client . Workspaces ( ctx , codersdk . WorkspaceFilter {
2022-06-06 19:43:16 +00:00
Name : workspace . Name [ 1 : len ( workspace . Name ) - 2 ] ,
} )
require . NoError ( t , err )
2022-11-10 18:25:46 +00:00
require . Len ( t , res . Workspaces , 1 )
require . Equal ( t , workspace . ID , res . Workspaces [ 0 ] . ID )
2022-06-06 19:43:16 +00:00
// no match
2022-11-10 18:25:46 +00:00
res , err = client . Workspaces ( ctx , codersdk . WorkspaceFilter {
2022-06-06 19:43:16 +00:00
Name : "$$$$" ,
} )
require . NoError ( t , err )
2022-11-10 18:25:46 +00:00
require . Len ( t , res . Workspaces , 0 )
2022-06-06 19:43:16 +00:00
} )
2024-03-11 16:37:15 +00:00
t . Run ( "IDs" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
alpha := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
bravo := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
// full match
res , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter {
FilterQuery : fmt . Sprintf ( "id:%s,%s" , alpha . ID , bravo . ID ) ,
} )
require . NoError ( t , err )
require . Len ( t , res . Workspaces , 2 )
require . True ( t , slices . ContainsFunc ( res . Workspaces , func ( workspace codersdk . Workspace ) bool {
return workspace . ID == alpha . ID
} ) , "alpha workspace" )
require . True ( t , slices . ContainsFunc ( res . Workspaces , func ( workspace codersdk . Workspace ) bool {
return workspace . ID == alpha . ID
} ) , "bravo workspace" )
// no match
res , err = client . Workspaces ( ctx , codersdk . WorkspaceFilter {
FilterQuery : fmt . Sprintf ( "id:%s" , uuid . NewString ( ) ) ,
} )
require . NoError ( t , err )
require . Len ( t , res . Workspaces , 0 )
} )
2022-06-14 13:46:33 +00:00
t . Run ( "Template" , func ( t * testing . T ) {
t . Parallel ( )
2022-09-04 16:28:09 +00:00
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
2022-06-14 13:46:33 +00:00
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
2023-05-23 08:06:33 +00:00
version2 := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version2 . ID )
2022-06-14 13:46:33 +00:00
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
2023-05-23 08:06:33 +00:00
template2 := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version2 . ID )
2022-06-14 13:46:33 +00:00
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
_ = coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template2 . ID )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2022-06-14 13:46:33 +00:00
// empty
2022-11-10 18:25:46 +00:00
res , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter { } )
2022-06-14 13:46:33 +00:00
require . NoError ( t , err )
2022-11-10 18:25:46 +00:00
require . Len ( t , res . Workspaces , 2 )
2022-06-14 13:46:33 +00:00
// single template
2022-11-10 18:25:46 +00:00
res , err = client . Workspaces ( ctx , codersdk . WorkspaceFilter {
2022-06-14 13:46:33 +00:00
Template : template . Name ,
} )
require . NoError ( t , err )
2022-11-10 18:25:46 +00:00
require . Len ( t , res . Workspaces , 1 )
require . Equal ( t , workspace . ID , res . Workspaces [ 0 ] . ID )
2022-06-14 13:46:33 +00:00
} )
2022-10-11 17:50:41 +00:00
t . Run ( "Status" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-10-11 17:50:41 +00:00
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
workspace1 := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
workspace2 := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
// wait for workspaces to be "running"
2023-10-03 17:02:56 +00:00
_ = coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace1 . LatestBuild . ID )
_ = coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace2 . LatestBuild . ID )
2022-10-11 17:50:41 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
// filter finds both running workspaces
ws1 , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter { } )
require . NoError ( t , err )
2022-11-10 18:25:46 +00:00
require . Len ( t , ws1 . Workspaces , 2 )
2022-10-11 17:50:41 +00:00
// stop workspace1
build1 := coderdtest . CreateWorkspaceBuild ( t , client , workspace1 , database . WorkspaceTransitionStop )
2023-10-03 17:02:56 +00:00
_ = coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , build1 . ID )
2022-10-11 17:50:41 +00:00
// filter finds one running workspace
ws2 , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter {
Status : "running" ,
} )
require . NoError ( t , err )
2022-11-10 18:25:46 +00:00
require . Len ( t , ws2 . Workspaces , 1 )
require . Equal ( t , workspace2 . ID , ws2 . Workspaces [ 0 ] . ID )
2022-10-11 17:50:41 +00:00
// stop workspace2
build2 := coderdtest . CreateWorkspaceBuild ( t , client , workspace2 , database . WorkspaceTransitionStop )
2023-10-03 17:02:56 +00:00
_ = coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , build2 . ID )
2022-10-11 17:50:41 +00:00
// filter finds no running workspaces
ws3 , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter {
Status : "running" ,
} )
require . NoError ( t , err )
2022-11-10 18:25:46 +00:00
require . Len ( t , ws3 . Workspaces , 0 )
2022-10-11 17:50:41 +00:00
} )
2022-06-14 13:46:33 +00:00
t . Run ( "FilterQuery" , func ( t * testing . T ) {
t . Parallel ( )
2022-09-04 16:28:09 +00:00
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
2022-06-14 13:46:33 +00:00
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
2023-05-23 08:06:33 +00:00
version2 := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version2 . ID )
2022-06-14 13:46:33 +00:00
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
2023-05-23 08:06:33 +00:00
template2 := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version2 . ID )
2022-06-14 13:46:33 +00:00
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
_ = coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template2 . ID )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2022-06-14 13:46:33 +00:00
// single workspace
2022-11-10 18:25:46 +00:00
res , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter {
2022-06-14 13:46:33 +00:00
FilterQuery : fmt . Sprintf ( "template:%s %s/%s" , template . Name , workspace . OwnerName , workspace . Name ) ,
} )
require . NoError ( t , err )
2022-11-10 18:25:46 +00:00
require . Len ( t , res . Workspaces , 1 )
require . Equal ( t , workspace . ID , res . Workspaces [ 0 ] . ID )
2022-06-14 13:46:33 +00:00
} )
2022-11-24 14:33:13 +00:00
t . Run ( "FilterQueryHasAgentConnecting" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , & coderdtest . Options {
IncludeProvisionerDaemon : true ,
} )
user := coderdtest . CreateFirstUser ( t , client )
authToken := uuid . NewString ( )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , & echo . Responses {
2023-03-21 18:03:38 +00:00
Parse : echo . ParseComplete ,
2023-08-25 06:10:15 +00:00
ProvisionPlan : echo . PlanComplete ,
2023-03-21 18:03:38 +00:00
ProvisionApply : echo . ProvisionApplyWithAgent ( authToken ) ,
2022-11-24 14:33:13 +00:00
} )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-11-24 14:33:13 +00:00
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2022-11-24 14:33:13 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
res , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter {
FilterQuery : fmt . Sprintf ( "has-agent:%s" , "connecting" ) ,
} )
require . NoError ( t , err )
require . Len ( t , res . Workspaces , 1 )
require . Equal ( t , workspace . ID , res . Workspaces [ 0 ] . ID )
} )
t . Run ( "FilterQueryHasAgentConnected" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , & coderdtest . Options {
IncludeProvisionerDaemon : true ,
} )
user := coderdtest . CreateFirstUser ( t , client )
authToken := uuid . NewString ( )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , & echo . Responses {
2023-03-21 18:03:38 +00:00
Parse : echo . ParseComplete ,
2023-08-25 06:10:15 +00:00
ProvisionPlan : echo . PlanComplete ,
2023-03-21 18:03:38 +00:00
ProvisionApply : echo . ProvisionApplyWithAgent ( authToken ) ,
2022-11-24 14:33:13 +00:00
} )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-11-24 14:33:13 +00:00
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2022-11-24 14:33:13 +00:00
2023-09-26 11:05:19 +00:00
_ = agenttest . New ( t , client . URL , authToken )
_ = coderdtest . AwaitWorkspaceAgents ( t , client , workspace . ID )
2022-11-24 14:33:13 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
res , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter {
FilterQuery : fmt . Sprintf ( "has-agent:%s" , "connected" ) ,
} )
require . NoError ( t , err )
require . Len ( t , res . Workspaces , 1 )
require . Equal ( t , workspace . ID , res . Workspaces [ 0 ] . ID )
} )
t . Run ( "FilterQueryHasAgentTimeout" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , & coderdtest . Options {
IncludeProvisionerDaemon : true ,
} )
user := coderdtest . CreateFirstUser ( t , client )
authToken := uuid . NewString ( )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , & echo . Responses {
Parse : echo . ParseComplete ,
2023-08-25 06:10:15 +00:00
ProvisionPlan : echo . PlanComplete ,
ProvisionApply : [ ] * proto . Response { {
Type : & proto . Response_Apply {
Apply : & proto . ApplyComplete {
2022-11-24 14:33:13 +00:00
Resources : [ ] * proto . Resource { {
Name : "example" ,
Type : "aws_instance" ,
Agents : [ ] * proto . Agent { {
Id : uuid . NewString ( ) ,
Auth : & proto . Agent_Token {
Token : authToken ,
} ,
ConnectionTimeoutSeconds : 1 ,
} } ,
} } ,
} ,
} ,
} } ,
} )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-11-24 14:33:13 +00:00
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2022-11-24 14:33:13 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitMedium )
defer cancel ( )
testutil . Eventually ( ctx , t , func ( ctx context . Context ) ( done bool ) {
workspaces , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter {
FilterQuery : fmt . Sprintf ( "has-agent:%s" , "timeout" ) ,
} )
require . NoError ( t , err )
return workspaces . Count == 1
} , testutil . IntervalMedium , "agent status timeout" )
} )
2023-12-08 00:09:35 +00:00
t . Run ( "Dormant" , func ( t * testing . T ) {
2023-08-04 00:46:02 +00:00
// this test has a licensed counterpart in enterprise/coderd/workspaces_test.go: FilterQueryHasDeletingByAndLicensed
t . Parallel ( )
2023-12-08 00:09:35 +00:00
client , db := coderdtest . NewWithDatabase ( t , nil )
2023-08-04 00:46:02 +00:00
user := coderdtest . CreateFirstUser ( t , client )
2023-12-08 00:09:35 +00:00
template := dbfake . TemplateVersion ( t , db ) . Seed ( database . TemplateVersion {
OrganizationID : user . OrganizationID ,
CreatedBy : user . UserID ,
} ) . Do ( ) . Template
2023-08-04 00:46:02 +00:00
// update template with inactivity ttl
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2023-12-08 00:09:35 +00:00
dormantWorkspace := dbfake . WorkspaceBuild ( t , db , database . Workspace {
TemplateID : template . ID ,
OwnerID : user . UserID ,
OrganizationID : user . OrganizationID ,
} ) . Do ( ) . Workspace
2023-08-04 00:46:02 +00:00
2023-08-24 18:25:54 +00:00
// Create another workspace to validate that we do not return active workspaces.
2023-12-08 00:09:35 +00:00
_ = dbfake . WorkspaceBuild ( t , db , database . Workspace {
TemplateID : template . ID ,
OwnerID : user . UserID ,
OrganizationID : user . OrganizationID ,
} ) . Do ( )
2023-08-04 00:46:02 +00:00
2023-08-24 18:25:54 +00:00
err := client . UpdateWorkspaceDormancy ( ctx , dormantWorkspace . ID , codersdk . UpdateWorkspaceDormancy {
Dormant : true ,
2023-08-04 00:46:02 +00:00
} )
require . NoError ( t , err )
2023-12-08 00:09:35 +00:00
// Test that no filter returns both workspaces.
res , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter { } )
require . NoError ( t , err )
require . Len ( t , res . Workspaces , 2 )
// Test that filtering for dormant only returns our dormant workspace.
res , err = client . Workspaces ( ctx , codersdk . WorkspaceFilter {
FilterQuery : "dormant:true" ,
2023-08-04 00:46:02 +00:00
} )
require . NoError ( t , err )
require . Len ( t , res . Workspaces , 1 )
2023-12-08 00:09:35 +00:00
require . Equal ( t , dormantWorkspace . ID , res . Workspaces [ 0 ] . ID )
2023-08-24 18:25:54 +00:00
require . NotNil ( t , res . Workspaces [ 0 ] . DormantAt )
2023-08-04 00:46:02 +00:00
} )
2023-08-22 13:41:58 +00:00
t . Run ( "LastUsed" , func ( t * testing . T ) {
t . Parallel ( )
2023-09-15 08:01:32 +00:00
2023-08-22 13:41:58 +00:00
client , _ , api := coderdtest . NewWithAPI ( t , & coderdtest . Options {
IncludeProvisionerDaemon : true ,
} )
user := coderdtest . CreateFirstUser ( t , client )
authToken := uuid . NewString ( )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , & echo . Responses {
Parse : echo . ParseComplete ,
2023-08-25 06:10:15 +00:00
ProvisionPlan : echo . PlanComplete ,
2023-08-22 13:41:58 +00:00
ProvisionApply : echo . ProvisionApplyWithAgent ( authToken ) ,
} )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
2023-10-03 17:02:56 +00:00
_ = coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2023-08-22 13:41:58 +00:00
// update template with inactivity ttl
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2023-09-01 16:50:12 +00:00
now := dbtime . Now ( )
2023-08-22 13:41:58 +00:00
before := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2023-10-03 17:02:56 +00:00
_ = coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , before . LatestBuild . ID )
2023-08-22 13:41:58 +00:00
after := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2023-10-03 17:02:56 +00:00
_ = coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , after . LatestBuild . ID )
2023-08-22 13:41:58 +00:00
//nolint:gocritic // Unit testing context
err := api . Database . UpdateWorkspaceLastUsedAt ( dbauthz . AsSystemRestricted ( ctx ) , database . UpdateWorkspaceLastUsedAtParams {
ID : before . ID ,
LastUsedAt : now . UTC ( ) . Add ( time . Hour * - 1 ) ,
} )
require . NoError ( t , err )
// Unit testing context
//nolint:gocritic // Unit testing context
err = api . Database . UpdateWorkspaceLastUsedAt ( dbauthz . AsSystemRestricted ( ctx ) , database . UpdateWorkspaceLastUsedAtParams {
ID : after . ID ,
LastUsedAt : now . UTC ( ) . Add ( time . Hour * 1 ) ,
} )
require . NoError ( t , err )
beforeRes , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter {
FilterQuery : fmt . Sprintf ( "last_used_before:%q" , now . Format ( time . RFC3339 ) ) ,
} )
require . NoError ( t , err )
require . Len ( t , beforeRes . Workspaces , 1 )
require . Equal ( t , before . ID , beforeRes . Workspaces [ 0 ] . ID )
afterRes , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter {
FilterQuery : fmt . Sprintf ( "last_used_after:%q" , now . Format ( time . RFC3339 ) ) ,
} )
require . NoError ( t , err )
require . Len ( t , afterRes . Workspaces , 1 )
require . Equal ( t , after . ID , afterRes . Workspaces [ 0 ] . ID )
} )
2024-01-23 17:52:06 +00:00
t . Run ( "Updated" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
// Workspace is up-to-date
res , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter {
FilterQuery : "outdated:false" ,
} )
require . NoError ( t , err )
require . Len ( t , res . Workspaces , 1 )
require . Equal ( t , workspace . ID , res . Workspaces [ 0 ] . ID )
res , err = client . Workspaces ( ctx , codersdk . WorkspaceFilter {
FilterQuery : "outdated:true" ,
} )
require . NoError ( t , err )
require . Len ( t , res . Workspaces , 0 )
// Now make it out of date
newTv := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil , func ( request * codersdk . CreateTemplateVersionRequest ) {
request . TemplateID = template . ID
} )
2024-01-29 15:29:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , newTv . ID )
2024-01-23 17:52:06 +00:00
err = client . UpdateActiveTemplateVersion ( ctx , template . ID , codersdk . UpdateActiveTemplateVersion {
ID : newTv . ID ,
} )
require . NoError ( t , err )
// Check the query again
res , err = client . Workspaces ( ctx , codersdk . WorkspaceFilter {
FilterQuery : "outdated:false" ,
} )
require . NoError ( t , err )
require . Len ( t , res . Workspaces , 0 )
res , err = client . Workspaces ( ctx , codersdk . WorkspaceFilter {
FilterQuery : "outdated:true" ,
} )
require . NoError ( t , err )
require . Len ( t , res . Workspaces , 1 )
require . Equal ( t , workspace . ID , res . Workspaces [ 0 ] . ID )
} )
2024-03-22 19:22:47 +00:00
t . Run ( "Params" , func ( t * testing . T ) {
t . Parallel ( )
const (
paramOneName = "one"
paramTwoName = "two"
paramThreeName = "three"
paramOptional = "optional"
)
makeParameters := func ( extra ... * proto . RichParameter ) * echo . Responses {
return & echo . Responses {
Parse : echo . ParseComplete ,
ProvisionPlan : [ ] * proto . Response {
{
Type : & proto . Response_Plan {
Plan : & proto . PlanComplete {
Parameters : append ( [ ] * proto . RichParameter {
{ Name : paramOneName , Description : "" , Mutable : true , Type : "string" } ,
{ Name : paramTwoName , DisplayName : "" , Description : "" , Mutable : true , Type : "string" } ,
{ Name : paramThreeName , Description : "" , Mutable : true , Type : "string" } ,
} , extra ... ) ,
} ,
} ,
} ,
} ,
ProvisionApply : echo . ApplyComplete ,
}
}
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , makeParameters ( & proto . RichParameter { Name : paramOptional , Description : "" , Mutable : true , Type : "string" } ) )
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
noOptionalVersion := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , makeParameters ( ) , func ( request * codersdk . CreateTemplateVersionRequest ) {
request . TemplateID = template . ID
} )
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , noOptionalVersion . ID )
// foo :: one=foo, two=bar, one=baz, optional=optional
foo := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , uuid . Nil , func ( request * codersdk . CreateWorkspaceRequest ) {
request . TemplateVersionID = version . ID
request . RichParameterValues = [ ] codersdk . WorkspaceBuildParameter {
{
Name : paramOneName ,
Value : "foo" ,
} ,
{
Name : paramTwoName ,
Value : "bar" ,
} ,
{
Name : paramThreeName ,
Value : "baz" ,
} ,
{
Name : paramOptional ,
Value : "optional" ,
} ,
}
} )
// bar :: one=foo, two=bar, three=baz, optional=optional
bar := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , uuid . Nil , func ( request * codersdk . CreateWorkspaceRequest ) {
request . TemplateVersionID = version . ID
request . RichParameterValues = [ ] codersdk . WorkspaceBuildParameter {
{
Name : paramOneName ,
Value : "bar" ,
} ,
{
Name : paramTwoName ,
Value : "bar" ,
} ,
{
Name : paramThreeName ,
Value : "baz" ,
} ,
{
Name : paramOptional ,
Value : "optional" ,
} ,
}
} )
// baz :: one=baz, two=baz, three=baz
baz := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , uuid . Nil , func ( request * codersdk . CreateWorkspaceRequest ) {
request . TemplateVersionID = noOptionalVersion . ID
request . RichParameterValues = [ ] codersdk . WorkspaceBuildParameter {
{
Name : paramOneName ,
Value : "unique" ,
} ,
{
Name : paramTwoName ,
Value : "baz" ,
} ,
{
Name : paramThreeName ,
Value : "baz" ,
} ,
}
} )
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
//nolint:tparallel,paralleltest
t . Run ( "has_param" , func ( t * testing . T ) {
// Checks the existence of a param value
// all match
all , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter {
FilterQuery : fmt . Sprintf ( "param:%s" , paramOneName ) ,
} )
require . NoError ( t , err )
expectIDs ( t , [ ] codersdk . Workspace { foo , bar , baz } , all . Workspaces )
// Some match
optional , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter {
FilterQuery : fmt . Sprintf ( "param:%s" , paramOptional ) ,
} )
require . NoError ( t , err )
expectIDs ( t , [ ] codersdk . Workspace { foo , bar } , optional . Workspaces )
// None match
none , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter {
FilterQuery : "param:not-a-param" ,
} )
require . NoError ( t , err )
require . Len ( t , none . Workspaces , 0 )
} )
//nolint:tparallel,paralleltest
t . Run ( "exact_param" , func ( t * testing . T ) {
// All match
all , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter {
FilterQuery : fmt . Sprintf ( "param:%s=%s" , paramThreeName , "baz" ) ,
} )
require . NoError ( t , err )
expectIDs ( t , [ ] codersdk . Workspace { foo , bar , baz } , all . Workspaces )
// Two match
two , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter {
FilterQuery : fmt . Sprintf ( "param:%s=%s" , paramTwoName , "bar" ) ,
} )
require . NoError ( t , err )
expectIDs ( t , [ ] codersdk . Workspace { foo , bar } , two . Workspaces )
// Only 1 matches
one , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter {
FilterQuery : fmt . Sprintf ( "param:%s=%s" , paramOneName , "foo" ) ,
} )
require . NoError ( t , err )
expectIDs ( t , [ ] codersdk . Workspace { foo } , one . Workspaces )
} )
//nolint:tparallel,paralleltest
t . Run ( "exact_param_and_has" , func ( t * testing . T ) {
all , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter {
FilterQuery : fmt . Sprintf ( "param:not=athing param:%s=%s param:%s=%s" , paramOptional , "optional" , paramOneName , "unique" ) ,
} )
require . NoError ( t , err )
expectIDs ( t , [ ] codersdk . Workspace { foo , bar , baz } , all . Workspaces )
} )
} )
2022-06-06 19:43:16 +00:00
}
2022-10-13 16:41:13 +00:00
func TestOffsetLimit ( t * testing . T ) {
t . Parallel ( )
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-10-13 16:41:13 +00:00
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
_ = coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
_ = coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
_ = coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2024-03-05 14:05:15 +00:00
// Case 1: empty finds all workspaces
2022-10-13 16:41:13 +00:00
ws , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter { } )
require . NoError ( t , err )
2022-11-10 18:25:46 +00:00
require . Len ( t , ws . Workspaces , 3 )
2022-10-13 16:41:13 +00:00
2024-03-05 14:05:15 +00:00
// Case 2: offset 1 finds 2 workspaces
2022-10-13 16:41:13 +00:00
ws , err = client . Workspaces ( ctx , codersdk . WorkspaceFilter {
Offset : 1 ,
} )
require . NoError ( t , err )
2022-11-10 18:25:46 +00:00
require . Len ( t , ws . Workspaces , 2 )
2022-10-13 16:41:13 +00:00
2024-03-05 14:05:15 +00:00
// Case 3: offset 1 limit 1 finds 1 workspace
2022-10-13 16:41:13 +00:00
ws , err = client . Workspaces ( ctx , codersdk . WorkspaceFilter {
Offset : 1 ,
Limit : 1 ,
} )
require . NoError ( t , err )
2022-11-10 18:25:46 +00:00
require . Len ( t , ws . Workspaces , 1 )
2022-10-13 16:41:13 +00:00
2024-03-05 14:05:15 +00:00
// Case 4: offset 3 finds no workspaces
2022-10-13 16:41:13 +00:00
ws , err = client . Workspaces ( ctx , codersdk . WorkspaceFilter {
Offset : 3 ,
} )
require . NoError ( t , err )
2022-11-10 18:25:46 +00:00
require . Len ( t , ws . Workspaces , 0 )
2024-03-05 08:24:43 +00:00
require . Equal ( t , ws . Count , 3 ) // can't find workspaces, but count is non-zero
2024-03-05 14:05:15 +00:00
// Case 5: offset out of range
ws , err = client . Workspaces ( ctx , codersdk . WorkspaceFilter {
Offset : math . MaxInt32 + 1 , // Potential risk: pq: OFFSET must not be negative
} )
require . Error ( t , err )
2022-10-20 17:23:14 +00:00
}
2022-04-07 09:03:35 +00:00
func TestWorkspaceUpdateAutostart ( t * testing . T ) {
t . Parallel ( )
2022-08-09 17:17:00 +00:00
dublinLoc := mustLocation ( t , "Europe/Dublin" )
2022-04-07 09:03:35 +00:00
testCases := [ ] struct {
name string
2022-06-02 10:23:34 +00:00
schedule * string
2022-04-07 09:03:35 +00:00
expectedError string
at time . Time
expectedNext time . Time
expectedInterval time . Duration
} {
{
name : "disable autostart" ,
2022-06-02 10:23:34 +00:00
schedule : ptr . Ref ( "" ) ,
2022-04-07 09:03:35 +00:00
expectedError : "" ,
} ,
{
name : "friday to monday" ,
2022-06-02 10:23:34 +00:00
schedule : ptr . Ref ( "CRON_TZ=Europe/Dublin 30 9 * * 1-5" ) ,
2022-04-07 09:03:35 +00:00
expectedError : "" ,
at : time . Date ( 2022 , 5 , 6 , 9 , 31 , 0 , 0 , dublinLoc ) ,
expectedNext : time . Date ( 2022 , 5 , 9 , 9 , 30 , 0 , 0 , dublinLoc ) ,
expectedInterval : 71 * time . Hour + 59 * time . Minute ,
} ,
{
name : "monday to tuesday" ,
2022-06-02 10:23:34 +00:00
schedule : ptr . Ref ( "CRON_TZ=Europe/Dublin 30 9 * * 1-5" ) ,
2022-04-07 09:03:35 +00:00
expectedError : "" ,
at : time . Date ( 2022 , 5 , 9 , 9 , 31 , 0 , 0 , dublinLoc ) ,
expectedNext : time . Date ( 2022 , 5 , 10 , 9 , 30 , 0 , 0 , dublinLoc ) ,
expectedInterval : 23 * time . Hour + 59 * time . Minute ,
} ,
{
// DST in Ireland began on Mar 27 in 2022 at 0100. Forward 1 hour.
name : "DST start" ,
2022-06-02 10:23:34 +00:00
schedule : ptr . Ref ( "CRON_TZ=Europe/Dublin 30 9 * * *" ) ,
2022-04-07 09:03:35 +00:00
expectedError : "" ,
at : time . Date ( 2022 , 3 , 26 , 9 , 31 , 0 , 0 , dublinLoc ) ,
expectedNext : time . Date ( 2022 , 3 , 27 , 9 , 30 , 0 , 0 , dublinLoc ) ,
expectedInterval : 22 * time . Hour + 59 * time . Minute ,
} ,
{
// DST in Ireland ends on Oct 30 in 2022 at 0200. Back 1 hour.
name : "DST end" ,
2022-06-02 10:23:34 +00:00
schedule : ptr . Ref ( "CRON_TZ=Europe/Dublin 30 9 * * *" ) ,
2022-04-07 09:03:35 +00:00
expectedError : "" ,
at : time . Date ( 2022 , 10 , 29 , 9 , 31 , 0 , 0 , dublinLoc ) ,
expectedNext : time . Date ( 2022 , 10 , 30 , 9 , 30 , 0 , 0 , dublinLoc ) ,
expectedInterval : 24 * time . Hour + 59 * time . Minute ,
} ,
{
name : "invalid location" ,
2022-06-02 10:23:34 +00:00
schedule : ptr . Ref ( "CRON_TZ=Imaginary/Place 30 9 * * 1-5" ) ,
2022-06-07 12:37:45 +00:00
expectedError : "parse schedule: provided bad location Imaginary/Place: unknown time zone Imaginary/Place" ,
2022-04-07 09:03:35 +00:00
} ,
{
name : "invalid schedule" ,
2022-06-02 10:23:34 +00:00
schedule : ptr . Ref ( "asdf asdf asdf " ) ,
2022-06-07 12:37:45 +00:00
expectedError : ` validate weekly schedule: expected schedule to consist of 5 fields with an optional CRON_TZ=<timezone> prefix ` ,
2022-04-18 16:04:48 +00:00
} ,
{
name : "only 3 values" ,
2022-06-02 10:23:34 +00:00
schedule : ptr . Ref ( "CRON_TZ=Europe/Dublin 30 9 *" ) ,
2022-06-07 12:37:45 +00:00
expectedError : ` validate weekly schedule: expected schedule to consist of 5 fields with an optional CRON_TZ=<timezone> prefix ` ,
2022-04-07 09:03:35 +00:00
} ,
}
for _ , testCase := range testCases {
testCase := testCase
t . Run ( testCase . name , func ( t * testing . T ) {
t . Parallel ( )
var (
2022-09-10 16:07:45 +00:00
auditor = audit . NewMock ( )
client = coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true , Auditor : auditor } )
2022-04-07 09:03:35 +00:00
user = coderdtest . CreateFirstUser ( t , client )
version = coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
2023-10-03 17:02:56 +00:00
_ = coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-04-07 09:03:35 +00:00
project = coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
2022-05-23 22:31:41 +00:00
workspace = coderdtest . CreateWorkspace ( t , client , user . OrganizationID , project . ID , func ( cwr * codersdk . CreateWorkspaceRequest ) {
cwr . AutostartSchedule = nil
2022-06-02 10:23:34 +00:00
cwr . TTLMillis = nil
2022-05-23 22:31:41 +00:00
} )
2022-04-07 09:03:35 +00:00
)
2022-11-22 18:22:56 +00:00
// await job to ensure audit logs for workspace_build start are created
2023-10-03 17:02:56 +00:00
_ = coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2022-11-22 18:22:56 +00:00
2022-04-07 09:03:35 +00:00
// ensure test invariant: new workspaces have no autostart schedule.
require . Empty ( t , workspace . AutostartSchedule , "expected newly-minted workspace to have no autostart schedule" )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2022-04-07 09:03:35 +00:00
err := client . UpdateWorkspaceAutostart ( ctx , workspace . ID , codersdk . UpdateWorkspaceAutostartRequest {
Schedule : testCase . schedule ,
} )
if testCase . expectedError != "" {
2022-06-03 21:48:09 +00:00
require . ErrorContains ( t , err , testCase . expectedError , "Invalid autostart schedule" )
2022-04-07 09:03:35 +00:00
return
}
require . NoError ( t , err , "expected no error setting workspace autostart schedule" )
updated , err := client . Workspace ( ctx , workspace . ID )
require . NoError ( t , err , "fetch updated workspace" )
2022-06-02 10:23:34 +00:00
if testCase . schedule == nil || * testCase . schedule == "" {
require . Nil ( t , updated . AutostartSchedule )
2022-04-07 09:03:35 +00:00
return
}
2022-06-02 10:23:34 +00:00
require . EqualValues ( t , * testCase . schedule , * updated . AutostartSchedule , "expected autostart schedule to equal requested" )
2023-09-04 13:48:25 +00:00
sched , err := cron . Weekly ( * updated . AutostartSchedule )
2022-04-07 09:03:35 +00:00
require . NoError ( t , err , "parse returned schedule" )
next := sched . Next ( testCase . at )
require . Equal ( t , testCase . expectedNext , next , "unexpected next scheduled autostart time" )
interval := next . Sub ( testCase . at )
require . Equal ( t , testCase . expectedInterval , interval , "unexpected interval" )
2022-09-10 16:07:45 +00:00
2023-03-21 19:19:09 +00:00
require . Eventually ( t , func ( ) bool {
2023-03-30 17:13:03 +00:00
if len ( auditor . AuditLogs ( ) ) < 7 {
2023-03-21 19:19:09 +00:00
return false
}
2023-03-30 17:13:03 +00:00
return auditor . AuditLogs ( ) [ 6 ] . Action == database . AuditActionWrite ||
auditor . AuditLogs ( ) [ 5 ] . Action == database . AuditActionWrite
2023-03-21 19:19:09 +00:00
} , testutil . WaitShort , testutil . IntervalFast )
2022-04-07 09:03:35 +00:00
} )
}
2023-04-04 12:48:35 +00:00
t . Run ( "CustomAutostartDisabledByTemplate" , func ( t * testing . T ) {
t . Parallel ( )
var (
tss = schedule . MockTemplateScheduleStore {
GetFn : func ( _ context . Context , _ database . Store , _ uuid . UUID ) ( schedule . TemplateScheduleOptions , error ) {
return schedule . TemplateScheduleOptions {
UserAutostartEnabled : false ,
UserAutostopEnabled : false ,
DefaultTTL : 0 ,
2023-08-29 18:35:05 +00:00
AutostopRequirement : schedule . TemplateAutostopRequirement { } ,
2023-04-04 12:48:35 +00:00
} , nil
} ,
SetFn : func ( _ context . Context , _ database . Store , tpl database . Template , _ schedule . TemplateScheduleOptions ) ( database . Template , error ) {
return tpl , nil
} ,
}
client = coderdtest . New ( t , & coderdtest . Options {
IncludeProvisionerDaemon : true ,
TemplateScheduleStore : tss ,
} )
user = coderdtest . CreateFirstUser ( t , client )
version = coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
2023-10-03 17:02:56 +00:00
_ = coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2023-04-04 12:48:35 +00:00
project = coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
workspace = coderdtest . CreateWorkspace ( t , client , user . OrganizationID , project . ID , func ( cwr * codersdk . CreateWorkspaceRequest ) {
cwr . AutostartSchedule = nil
cwr . TTLMillis = nil
} )
)
// await job to ensure audit logs for workspace_build start are created
2023-10-03 17:02:56 +00:00
_ = coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2023-04-04 12:48:35 +00:00
// ensure test invariant: new workspaces have no autostart schedule.
require . Empty ( t , workspace . AutostartSchedule , "expected newly-minted workspace to have no autostart schedule" )
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
err := client . UpdateWorkspaceAutostart ( ctx , workspace . ID , codersdk . UpdateWorkspaceAutostartRequest {
Schedule : ptr . Ref ( "CRON_TZ=Europe/Dublin 30 9 * * 1-5" ) ,
} )
require . ErrorContains ( t , err , "Autostart is not allowed for workspaces using this template" )
} )
2022-04-07 09:03:35 +00:00
t . Run ( "NotFound" , func ( t * testing . T ) {
2023-04-04 12:48:35 +00:00
t . Parallel ( )
2022-04-07 09:03:35 +00:00
var (
client = coderdtest . New ( t , nil )
_ = coderdtest . CreateFirstUser ( t , client )
wsid = uuid . New ( )
req = codersdk . UpdateWorkspaceAutostartRequest {
2022-06-02 10:23:34 +00:00
Schedule : ptr . Ref ( "9 30 1-5" ) ,
2022-04-07 09:03:35 +00:00
}
)
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2022-04-07 09:03:35 +00:00
err := client . UpdateWorkspaceAutostart ( ctx , wsid , req )
require . IsType ( t , err , & codersdk . Error { } , "expected codersdk.Error" )
coderSDKErr , _ := err . ( * codersdk . Error ) //nolint:errorlint
require . Equal ( t , coderSDKErr . StatusCode ( ) , 404 , "expected status code 404" )
2022-06-14 15:14:05 +00:00
require . Contains ( t , coderSDKErr . Message , "Resource not found" , "unexpected response code" )
2022-04-07 09:03:35 +00:00
} )
}
2022-05-26 17:08:11 +00:00
func TestWorkspaceUpdateTTL ( t * testing . T ) {
2022-04-07 09:03:35 +00:00
t . Parallel ( )
testCases := [ ] struct {
2022-06-14 16:09:24 +00:00
name string
ttlMillis * int64
expectedError string
modifyTemplate func ( * codersdk . CreateTemplateRequest )
2022-04-07 09:03:35 +00:00
} {
{
2022-06-14 16:09:24 +00:00
name : "disable ttl" ,
ttlMillis : nil ,
expectedError : "" ,
2022-11-23 15:30:38 +00:00
modifyTemplate : func ( ctr * codersdk . CreateTemplateRequest ) {
ctr . DefaultTTLMillis = ptr . Ref ( ( 8 * time . Hour ) . Milliseconds ( ) )
} ,
2022-06-09 21:10:24 +00:00
} ,
{
2022-06-14 16:09:24 +00:00
name : "update ttl" ,
ttlMillis : ptr . Ref ( 12 * time . Hour . Milliseconds ( ) ) ,
expectedError : "" ,
2022-11-23 15:30:38 +00:00
modifyTemplate : func ( ctr * codersdk . CreateTemplateRequest ) {
ctr . DefaultTTLMillis = ptr . Ref ( ( 8 * time . Hour ) . Milliseconds ( ) )
} ,
2022-04-07 09:03:35 +00:00
} ,
{
2022-05-30 19:19:17 +00:00
name : "below minimum ttl" ,
2022-06-02 10:23:34 +00:00
ttlMillis : ptr . Ref ( ( 30 * time . Second ) . Milliseconds ( ) ) ,
2022-07-27 21:20:02 +00:00
expectedError : "time until shutdown must be at least one minute" ,
2022-05-30 19:19:17 +00:00
} ,
{
2022-06-14 16:09:24 +00:00
name : "minimum ttl" ,
ttlMillis : ptr . Ref ( time . Minute . Milliseconds ( ) ) ,
expectedError : "" ,
2022-05-30 19:19:17 +00:00
} ,
{
2022-06-14 16:09:24 +00:00
name : "maximum ttl" ,
2023-07-19 13:43:10 +00:00
ttlMillis : ptr . Ref ( ( 24 * 30 * time . Hour ) . Milliseconds ( ) ) ,
2022-06-14 16:09:24 +00:00
expectedError : "" ,
2022-04-07 09:03:35 +00:00
} ,
2022-05-30 19:19:17 +00:00
{
name : "above maximum ttl" ,
2023-07-19 13:43:10 +00:00
ttlMillis : ptr . Ref ( ( 24 * 30 * time . Hour + time . Minute ) . Milliseconds ( ) ) ,
expectedError : "time until shutdown must be less than 30 days" ,
2022-05-30 19:19:17 +00:00
} ,
2022-04-07 09:03:35 +00:00
}
for _ , testCase := range testCases {
testCase := testCase
t . Run ( testCase . name , func ( t * testing . T ) {
t . Parallel ( )
2022-06-07 12:37:45 +00:00
mutators := make ( [ ] func ( * codersdk . CreateTemplateRequest ) , 0 )
if testCase . modifyTemplate != nil {
mutators = append ( mutators , testCase . modifyTemplate )
}
2022-04-07 09:03:35 +00:00
var (
2022-09-10 16:07:45 +00:00
auditor = audit . NewMock ( )
client = coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true , Auditor : auditor } )
2022-04-07 09:03:35 +00:00
user = coderdtest . CreateFirstUser ( t , client )
version = coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
2023-10-03 17:02:56 +00:00
_ = coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-06-07 12:37:45 +00:00
project = coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID , mutators ... )
2022-05-23 22:31:41 +00:00
workspace = coderdtest . CreateWorkspace ( t , client , user . OrganizationID , project . ID , func ( cwr * codersdk . CreateWorkspaceRequest ) {
cwr . AutostartSchedule = nil
2022-06-02 10:23:34 +00:00
cwr . TTLMillis = nil
2022-05-23 22:31:41 +00:00
} )
2023-10-03 17:02:56 +00:00
_ = coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2022-04-07 09:03:35 +00:00
)
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2022-05-19 19:09:27 +00:00
err := client . UpdateWorkspaceTTL ( ctx , workspace . ID , codersdk . UpdateWorkspaceTTLRequest {
2022-06-02 10:23:34 +00:00
TTLMillis : testCase . ttlMillis ,
2022-04-07 09:03:35 +00:00
} )
if testCase . expectedError != "" {
2022-05-30 19:19:17 +00:00
require . ErrorContains ( t , err , testCase . expectedError , "unexpected error when setting workspace autostop schedule" )
2022-04-07 09:03:35 +00:00
return
}
require . NoError ( t , err , "expected no error setting workspace autostop schedule" )
updated , err := client . Workspace ( ctx , workspace . ID )
require . NoError ( t , err , "fetch updated workspace" )
2022-06-02 10:23:34 +00:00
require . Equal ( t , testCase . ttlMillis , updated . TTLMillis , "expected autostop ttl to equal requested" )
2022-09-10 16:07:45 +00:00
2023-03-21 16:08:39 +00:00
require . Eventually ( t , func ( ) bool {
2023-03-30 17:13:03 +00:00
if len ( auditor . AuditLogs ( ) ) != 7 {
2023-03-21 16:08:39 +00:00
return false
}
2023-03-30 17:13:03 +00:00
return auditor . AuditLogs ( ) [ 6 ] . Action == database . AuditActionWrite ||
auditor . AuditLogs ( ) [ 5 ] . Action == database . AuditActionWrite
2023-03-21 16:08:39 +00:00
} , testutil . WaitMedium , testutil . IntervalFast , "expected audit log to be written" )
2022-04-07 09:03:35 +00:00
} )
}
2023-04-04 12:48:35 +00:00
t . Run ( "CustomAutostopDisabledByTemplate" , func ( t * testing . T ) {
t . Parallel ( )
var (
tss = schedule . MockTemplateScheduleStore {
GetFn : func ( _ context . Context , _ database . Store , _ uuid . UUID ) ( schedule . TemplateScheduleOptions , error ) {
return schedule . TemplateScheduleOptions {
UserAutostartEnabled : false ,
UserAutostopEnabled : false ,
DefaultTTL : 0 ,
2023-08-29 18:35:05 +00:00
AutostopRequirement : schedule . TemplateAutostopRequirement { } ,
2023-04-04 12:48:35 +00:00
} , nil
} ,
SetFn : func ( _ context . Context , _ database . Store , tpl database . Template , _ schedule . TemplateScheduleOptions ) ( database . Template , error ) {
return tpl , nil
} ,
}
client = coderdtest . New ( t , & coderdtest . Options {
IncludeProvisionerDaemon : true ,
TemplateScheduleStore : tss ,
} )
user = coderdtest . CreateFirstUser ( t , client )
version = coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
2023-10-03 17:02:56 +00:00
_ = coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2023-04-04 12:48:35 +00:00
project = coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
workspace = coderdtest . CreateWorkspace ( t , client , user . OrganizationID , project . ID , func ( cwr * codersdk . CreateWorkspaceRequest ) {
cwr . AutostartSchedule = nil
cwr . TTLMillis = nil
} )
)
// await job to ensure audit logs for workspace_build start are created
2023-10-03 17:02:56 +00:00
_ = coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2023-04-04 12:48:35 +00:00
// ensure test invariant: new workspaces have no autostart schedule.
require . Empty ( t , workspace . AutostartSchedule , "expected newly-minted workspace to have no autostart schedule" )
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
err := client . UpdateWorkspaceTTL ( ctx , workspace . ID , codersdk . UpdateWorkspaceTTLRequest {
TTLMillis : ptr . Ref ( time . Hour . Milliseconds ( ) ) ,
} )
require . ErrorContains ( t , err , "Custom autostop TTL is not allowed for workspaces using this template" )
} )
2022-04-07 09:03:35 +00:00
t . Run ( "NotFound" , func ( t * testing . T ) {
2023-04-04 12:48:35 +00:00
t . Parallel ( )
2022-04-07 09:03:35 +00:00
var (
client = coderdtest . New ( t , nil )
_ = coderdtest . CreateFirstUser ( t , client )
wsid = uuid . New ( )
2022-05-19 19:09:27 +00:00
req = codersdk . UpdateWorkspaceTTLRequest {
2022-06-02 10:23:34 +00:00
TTLMillis : ptr . Ref ( time . Hour . Milliseconds ( ) ) ,
2022-04-07 09:03:35 +00:00
}
)
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2022-05-19 19:09:27 +00:00
err := client . UpdateWorkspaceTTL ( ctx , wsid , req )
2022-04-07 09:03:35 +00:00
require . IsType ( t , err , & codersdk . Error { } , "expected codersdk.Error" )
coderSDKErr , _ := err . ( * codersdk . Error ) //nolint:errorlint
require . Equal ( t , coderSDKErr . StatusCode ( ) , 404 , "expected status code 404" )
2022-06-14 15:14:05 +00:00
require . Contains ( t , coderSDKErr . Message , "Resource not found" , "unexpected response code" )
2022-04-07 09:03:35 +00:00
} )
}
2022-05-26 17:08:11 +00:00
func TestWorkspaceExtend ( t * testing . T ) {
t . Parallel ( )
var (
2022-06-14 21:39:15 +00:00
ttl = 8 * time . Hour
newDeadline = time . Now ( ) . Add ( ttl + time . Hour ) . UTC ( )
2022-09-04 16:28:09 +00:00
client = coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
2022-05-26 17:08:11 +00:00
user = coderdtest . CreateFirstUser ( t , client )
version = coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
2023-10-03 17:02:56 +00:00
_ = coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-06-14 21:39:15 +00:00
template = coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
workspace = coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID , func ( cwr * codersdk . CreateWorkspaceRequest ) {
cwr . TTLMillis = ptr . Ref ( ttl . Milliseconds ( ) )
} )
2023-10-03 17:02:56 +00:00
_ = coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2022-05-26 17:08:11 +00:00
)
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2022-05-26 17:08:11 +00:00
workspace , err := client . Workspace ( ctx , workspace . ID )
require . NoError ( t , err , "fetch provisioned workspace" )
2022-08-25 16:10:42 +00:00
oldDeadline := workspace . LatestBuild . Deadline . Time
2022-05-26 17:08:11 +00:00
// Updating the deadline should succeed
req := codersdk . PutExtendWorkspaceRequest {
Deadline : newDeadline ,
}
err = client . PutExtendWorkspace ( ctx , workspace . ID , req )
require . NoError ( t , err , "failed to extend workspace" )
// Ensure deadline set correctly
updated , err := client . Workspace ( ctx , workspace . ID )
require . NoError ( t , err , "failed to fetch updated workspace" )
2022-08-25 16:10:42 +00:00
require . WithinDuration ( t , newDeadline , updated . LatestBuild . Deadline . Time , time . Minute )
2022-05-26 17:08:11 +00:00
// Zero time should fail
err = client . PutExtendWorkspace ( ctx , workspace . ID , codersdk . PutExtendWorkspaceRequest {
Deadline : time . Time { } ,
} )
2022-05-27 14:45:22 +00:00
require . ErrorContains ( t , err , "deadline: Validation failed for tag \"required\" with value: \"0001-01-01 00:00:00 +0000 UTC\"" , "setting an empty deadline on a workspace should fail" )
2022-05-26 17:08:11 +00:00
2022-06-15 14:32:02 +00:00
// Updating with a deadline less than 30 minutes in the future should fail
deadlineTooSoon := time . Now ( ) . Add ( 15 * time . Minute ) // XXX: time.Now
2022-06-14 21:39:15 +00:00
err = client . PutExtendWorkspace ( ctx , workspace . ID , codersdk . PutExtendWorkspaceRequest {
Deadline : deadlineTooSoon ,
} )
2022-07-29 14:01:17 +00:00
require . ErrorContains ( t , err , "unexpected status code 400: Cannot extend workspace: new deadline must be at least 30 minutes in the future" , "setting a deadline less than 30 minutes in the future should fail" )
2022-06-14 21:39:15 +00:00
// Updating with a deadline 30 minutes in the future should succeed
deadlineJustSoonEnough := time . Now ( ) . Add ( 30 * time . Minute )
2022-05-26 17:08:11 +00:00
err = client . PutExtendWorkspace ( ctx , workspace . ID , codersdk . PutExtendWorkspaceRequest {
2022-06-14 21:39:15 +00:00
Deadline : deadlineJustSoonEnough ,
2022-05-26 17:08:11 +00:00
} )
2022-06-14 21:39:15 +00:00
require . NoError ( t , err , "setting a deadline at least 30 minutes in the future should succeed" )
2022-05-30 19:19:17 +00:00
2022-06-14 21:39:15 +00:00
// Updating with a deadline an hour before the previous deadline should succeed
2022-05-30 19:19:17 +00:00
err = client . PutExtendWorkspace ( ctx , workspace . ID , codersdk . PutExtendWorkspaceRequest {
2022-06-14 21:39:15 +00:00
Deadline : oldDeadline . Add ( - time . Hour ) ,
2022-05-30 19:19:17 +00:00
} )
2022-06-14 21:39:15 +00:00
require . NoError ( t , err , "setting an earlier deadline should not fail" )
2022-05-26 17:08:11 +00:00
// Ensure deadline still set correctly
updated , err = client . Workspace ( ctx , workspace . ID )
require . NoError ( t , err , "failed to fetch updated workspace" )
2022-08-25 16:10:42 +00:00
require . WithinDuration ( t , oldDeadline . Add ( - time . Hour ) , updated . LatestBuild . Deadline . Time , time . Minute )
2022-05-26 17:08:11 +00:00
}
2023-10-06 09:27:12 +00:00
func TestWorkspaceUpdateAutomaticUpdates_OK ( t * testing . T ) {
t . Parallel ( )
var (
auditor = audit . NewMock ( )
adminClient = coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true , Auditor : auditor } )
admin = coderdtest . CreateFirstUser ( t , adminClient )
client , user = coderdtest . CreateAnotherUser ( t , adminClient , admin . OrganizationID )
version = coderdtest . CreateTemplateVersion ( t , adminClient , admin . OrganizationID , nil )
_ = coderdtest . AwaitTemplateVersionJobCompleted ( t , adminClient , version . ID )
project = coderdtest . CreateTemplate ( t , adminClient , admin . OrganizationID , version . ID )
workspace = coderdtest . CreateWorkspace ( t , client , admin . OrganizationID , project . ID , func ( cwr * codersdk . CreateWorkspaceRequest ) {
cwr . AutostartSchedule = nil
cwr . TTLMillis = nil
cwr . AutomaticUpdates = codersdk . AutomaticUpdatesNever
} )
)
// await job to ensure audit logs for workspace_build start are created
_ = coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
// ensure test invariant: new workspaces have automatic updates set to never
require . Equal ( t , codersdk . AutomaticUpdatesNever , workspace . AutomaticUpdates , "expected newly-minted workspace to automatic updates set to never" )
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
err := client . UpdateWorkspaceAutomaticUpdates ( ctx , workspace . ID , codersdk . UpdateWorkspaceAutomaticUpdatesRequest {
AutomaticUpdates : codersdk . AutomaticUpdatesAlways ,
} )
require . NoError ( t , err )
updated , err := client . Workspace ( ctx , workspace . ID )
require . NoError ( t , err )
require . Equal ( t , codersdk . AutomaticUpdatesAlways , updated . AutomaticUpdates )
require . Eventually ( t , func ( ) bool {
2024-01-10 08:45:32 +00:00
var found bool
for _ , l := range auditor . AuditLogs ( ) {
if l . Action == database . AuditActionWrite &&
l . UserID == user . ID &&
l . ResourceID == workspace . ID {
found = true
break
}
}
return found
} , testutil . WaitShort , testutil . IntervalFast , "did not find expected audit log" )
2023-10-06 09:27:12 +00:00
}
func TestUpdateWorkspaceAutomaticUpdates_NotFound ( t * testing . T ) {
t . Parallel ( )
var (
client = coderdtest . New ( t , nil )
_ = coderdtest . CreateFirstUser ( t , client )
wsid = uuid . New ( )
req = codersdk . UpdateWorkspaceAutomaticUpdatesRequest {
AutomaticUpdates : codersdk . AutomaticUpdatesNever ,
}
)
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
err := client . UpdateWorkspaceAutomaticUpdates ( ctx , wsid , req )
require . IsType ( t , err , & codersdk . Error { } , "expected codersdk.Error" )
coderSDKErr , _ := err . ( * codersdk . Error ) //nolint:errorlint
require . Equal ( t , coderSDKErr . StatusCode ( ) , 404 , "expected status code 404" )
require . Contains ( t , coderSDKErr . Message , "Resource not found" , "unexpected response code" )
}
2022-05-18 21:16:26 +00:00
func TestWorkspaceWatcher ( t * testing . T ) {
t . Parallel ( )
2023-12-15 18:38:47 +00:00
client , closeFunc := coderdtest . NewWithProvisionerCloser ( t , & coderdtest . Options {
IncludeProvisionerDaemon : true ,
AllowWorkspaceRenames : true ,
} )
2023-07-14 23:07:48 +00:00
defer closeFunc . Close ( )
2022-05-18 21:16:26 +00:00
user := coderdtest . CreateFirstUser ( t , client )
2022-11-07 15:25:18 +00:00
authToken := uuid . NewString ( )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , & echo . Responses {
2022-11-11 22:45:58 +00:00
Parse : echo . ParseComplete ,
2023-08-25 06:10:15 +00:00
ProvisionPlan : echo . PlanComplete ,
ProvisionApply : [ ] * proto . Response { {
Type : & proto . Response_Apply {
Apply : & proto . ApplyComplete {
2022-11-07 15:25:18 +00:00
Resources : [ ] * proto . Resource { {
Name : "example" ,
Type : "aws_instance" ,
Agents : [ ] * proto . Agent { {
Id : uuid . NewString ( ) ,
Auth : & proto . Agent_Token {
Token : authToken ,
} ,
2022-11-22 11:01:28 +00:00
ConnectionTimeoutSeconds : 1 ,
2022-11-07 15:25:18 +00:00
} } ,
} } ,
} ,
} ,
} } ,
} )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-05-18 21:16:26 +00:00
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
2022-05-18 21:16:26 +00:00
defer cancel ( )
2022-08-09 17:17:00 +00:00
2022-11-07 15:25:18 +00:00
wc , err := client . WatchWorkspace ( ctx , workspace . ID )
2022-08-09 17:17:00 +00:00
require . NoError ( t , err )
2022-11-22 11:01:28 +00:00
// Wait events are easier to debug with timestamped logs.
logger := slogtest . Make ( t , nil ) . Named ( t . Name ( ) ) . Leveled ( slog . LevelDebug )
2023-02-13 09:54:43 +00:00
wait := func ( event string , ready func ( w codersdk . Workspace ) bool ) {
for {
select {
case <- ctx . Done ( ) :
require . FailNow ( t , "timed out waiting for event" , event )
case w , ok := <- wc :
require . True ( t , ok , "watch channel closed: %s" , event )
if ready == nil || ready ( w ) {
2023-08-09 12:09:25 +00:00
logger . Info ( ctx , "done waiting for event" ,
slog . F ( "event" , event ) ,
slog . F ( "workspace" , w ) )
2023-02-13 09:54:43 +00:00
return
}
2023-08-09 12:09:25 +00:00
logger . Info ( ctx , "skipped update for event" ,
slog . F ( "event" , event ) ,
slog . F ( "workspace" , w ) )
2023-02-13 09:54:43 +00:00
}
2022-11-07 15:25:18 +00:00
}
}
2022-08-09 17:17:00 +00:00
2022-11-07 15:25:18 +00:00
coderdtest . CreateWorkspaceBuild ( t , client , workspace , database . WorkspaceTransitionStart )
2023-02-13 09:54:43 +00:00
wait ( "workspace build being created" , nil )
wait ( "workspace build being acquired" , nil )
wait ( "workspace build completing" , nil )
2022-11-22 11:01:28 +00:00
// Unfortunately, this will add ~1s to the test due to the granularity
// of agent timeout seconds. However, if we don't do this we won't know
// which trigger we received when waiting for connection.
//
// Note that the first timeout is from `coderdtest.CreateWorkspace` and
// the latter is from `coderdtest.CreateWorkspaceBuild`.
2023-02-13 09:54:43 +00:00
wait ( "agent timeout after create" , nil )
wait ( "agent timeout after start" , nil )
2022-11-07 15:25:18 +00:00
2023-09-26 11:05:19 +00:00
agt := agenttest . New ( t , client . URL , authToken )
_ = coderdtest . AwaitWorkspaceAgents ( t , client , workspace . ID )
2022-11-07 15:25:18 +00:00
2023-02-13 09:54:43 +00:00
wait ( "agent connected/ready" , func ( w codersdk . Workspace ) bool {
return w . LatestBuild . Resources [ 0 ] . Agents [ 0 ] . Status == codersdk . WorkspaceAgentConnected &&
w . LatestBuild . Resources [ 0 ] . Agents [ 0 ] . LifecycleState == codersdk . WorkspaceAgentLifecycleReady
} )
2023-09-26 11:05:19 +00:00
agt . Close ( )
2023-02-13 09:54:43 +00:00
wait ( "agent disconnected" , func ( w codersdk . Workspace ) bool {
return w . LatestBuild . Resources [ 0 ] . Agents [ 0 ] . Status == codersdk . WorkspaceAgentDisconnected
} )
2022-11-07 15:25:18 +00:00
err = client . UpdateWorkspace ( ctx , workspace . ID , codersdk . UpdateWorkspaceRequest {
Name : "another" ,
} )
require . NoError ( t , err )
2023-02-13 09:54:43 +00:00
wait ( "update workspace name" , nil )
2022-11-07 15:25:18 +00:00
2023-07-14 23:07:48 +00:00
// Add a new version that will fail.
2023-07-19 23:03:56 +00:00
badVersion := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , & echo . Responses {
2023-07-14 23:07:48 +00:00
Parse : echo . ParseComplete ,
2023-08-25 06:10:15 +00:00
ProvisionPlan : echo . PlanComplete ,
ProvisionApply : [ ] * proto . Response { {
Type : & proto . Response_Apply {
Apply : & proto . ApplyComplete {
2023-07-14 23:07:48 +00:00
Error : "test error" ,
} ,
} ,
} } ,
} , func ( req * codersdk . CreateTemplateVersionRequest ) {
req . TemplateID = template . ID
} )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , badVersion . ID )
2022-11-09 17:01:34 +00:00
err = client . UpdateActiveTemplateVersion ( ctx , template . ID , codersdk . UpdateActiveTemplateVersion {
2023-07-19 23:03:56 +00:00
ID : badVersion . ID ,
2022-11-09 17:01:34 +00:00
} )
require . NoError ( t , err )
2023-02-13 09:54:43 +00:00
wait ( "update active template version" , nil )
2022-11-09 17:01:34 +00:00
2023-07-14 23:07:48 +00:00
// Build with the new template; should end up with a failure state.
_ = coderdtest . CreateWorkspaceBuild ( t , client , workspace , database . WorkspaceTransitionStart , func ( req * codersdk . CreateWorkspaceBuildRequest ) {
2023-07-19 23:03:56 +00:00
req . TemplateVersionID = badVersion . ID
2023-07-14 23:07:48 +00:00
} )
2023-07-18 18:58:50 +00:00
// We want to verify pending state here, but it's possible that we reach
// failed state fast enough that we never see pending.
2023-08-09 12:09:25 +00:00
sawFailed := false
2023-07-18 18:58:50 +00:00
wait ( "workspace build pending or failed" , func ( w codersdk . Workspace ) bool {
2023-08-09 12:09:25 +00:00
switch w . LatestBuild . Status {
case codersdk . WorkspaceStatusPending :
return true
case codersdk . WorkspaceStatusFailed :
sawFailed = true
return true
default :
return false
}
2023-07-14 23:07:48 +00:00
} )
2023-08-09 12:09:25 +00:00
if ! sawFailed {
wait ( "workspace build failed" , func ( w codersdk . Workspace ) bool {
return w . LatestBuild . Status == codersdk . WorkspaceStatusFailed
} )
}
2023-07-19 23:03:56 +00:00
closeFunc . Close ( )
build := coderdtest . CreateWorkspaceBuild ( t , client , workspace , database . WorkspaceTransitionStart )
wait ( "first is for the workspace build itself" , nil )
err = client . CancelWorkspaceBuild ( ctx , build . ID )
require . NoError ( t , err )
wait ( "second is for the build cancel" , nil )
2022-05-18 21:16:26 +00:00
}
2022-05-19 19:09:27 +00:00
func mustLocation ( t * testing . T , location string ) * time . Location {
t . Helper ( )
loc , err := time . LoadLocation ( location )
if err != nil {
t . Errorf ( "failed to load location %s: %s" , location , err . Error ( ) )
}
return loc
}
2022-10-03 21:01:13 +00:00
func TestWorkspaceResource ( t * testing . T ) {
t . Parallel ( )
t . Run ( "Get" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , & echo . Responses {
Parse : echo . ParseComplete ,
2023-08-25 06:10:15 +00:00
ProvisionApply : [ ] * proto . Response { {
Type : & proto . Response_Apply {
Apply : & proto . ApplyComplete {
2022-10-03 21:01:13 +00:00
Resources : [ ] * proto . Resource { {
Name : "beta" ,
Type : "example" ,
Icon : "/icon/server.svg" ,
Agents : [ ] * proto . Agent { {
Id : "something" ,
Name : "b" ,
Auth : & proto . Agent_Token { } ,
} , {
Id : "another" ,
Name : "a" ,
Auth : & proto . Agent_Token { } ,
} } ,
} , {
Name : "alpha" ,
Type : "example" ,
} } ,
} ,
} ,
} } ,
} )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-10-03 21:01:13 +00:00
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2022-10-03 21:01:13 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
workspace , err := client . Workspace ( ctx , workspace . ID )
require . NoError ( t , err )
require . Len ( t , workspace . LatestBuild . Resources [ 0 ] . Agents , 2 )
// Ensure Icon is present
require . Equal ( t , "/icon/server.svg" , workspace . LatestBuild . Resources [ 0 ] . Icon )
} )
t . Run ( "Apps" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , & coderdtest . Options {
IncludeProvisionerDaemon : true ,
} )
user := coderdtest . CreateFirstUser ( t , client )
apps := [ ] * proto . App {
{
2022-10-28 17:41:31 +00:00
Slug : "code-server" ,
DisplayName : "code-server" ,
Command : "some-command" ,
Url : "http://localhost:3000" ,
Icon : "/code.svg" ,
2022-10-03 21:01:13 +00:00
} ,
{
2022-10-28 17:41:31 +00:00
Slug : "code-server-2" ,
DisplayName : "code-server-2" ,
Command : "some-command" ,
Url : "http://localhost:3000" ,
Icon : "/code.svg" ,
2022-10-03 21:01:13 +00:00
Healthcheck : & proto . Healthcheck {
Url : "http://localhost:3000" ,
Interval : 5 ,
Threshold : 6 ,
} ,
} ,
}
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , & echo . Responses {
Parse : echo . ParseComplete ,
2023-08-25 06:10:15 +00:00
ProvisionApply : [ ] * proto . Response { {
Type : & proto . Response_Apply {
Apply : & proto . ApplyComplete {
2022-10-03 21:01:13 +00:00
Resources : [ ] * proto . Resource { {
Name : "some" ,
Type : "example" ,
Agents : [ ] * proto . Agent { {
Id : "something" ,
Auth : & proto . Agent_Token { } ,
Apps : apps ,
} } ,
} } ,
} ,
} ,
} } ,
} )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-10-03 21:01:13 +00:00
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2022-10-03 21:01:13 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
workspace , err := client . Workspace ( ctx , workspace . ID )
require . NoError ( t , err )
require . Len ( t , workspace . LatestBuild . Resources [ 0 ] . Agents , 1 )
agent := workspace . LatestBuild . Resources [ 0 ] . Agents [ 0 ]
require . Len ( t , agent . Apps , 2 )
got := agent . Apps [ 0 ]
app := apps [ 0 ]
require . EqualValues ( t , app . Command , got . Command )
require . EqualValues ( t , app . Icon , got . Icon )
2022-10-28 17:41:31 +00:00
require . EqualValues ( t , app . DisplayName , got . DisplayName )
2022-10-03 21:01:13 +00:00
require . EqualValues ( t , codersdk . WorkspaceAppHealthDisabled , got . Health )
require . EqualValues ( t , "" , got . Healthcheck . URL )
require . EqualValues ( t , 0 , got . Healthcheck . Interval )
require . EqualValues ( t , 0 , got . Healthcheck . Threshold )
got = agent . Apps [ 1 ]
app = apps [ 1 ]
require . EqualValues ( t , app . Command , got . Command )
require . EqualValues ( t , app . Icon , got . Icon )
2022-10-28 17:41:31 +00:00
require . EqualValues ( t , app . DisplayName , got . DisplayName )
2022-10-03 21:01:13 +00:00
require . EqualValues ( t , codersdk . WorkspaceAppHealthInitializing , got . Health )
require . EqualValues ( t , app . Healthcheck . Url , got . Healthcheck . URL )
require . EqualValues ( t , app . Healthcheck . Interval , got . Healthcheck . Interval )
require . EqualValues ( t , app . Healthcheck . Threshold , got . Healthcheck . Threshold )
} )
2024-02-12 14:11:31 +00:00
t . Run ( "Apps_DisplayOrder" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , & coderdtest . Options {
IncludeProvisionerDaemon : true ,
} )
user := coderdtest . CreateFirstUser ( t , client )
apps := [ ] * proto . App {
{
Slug : "aaa" ,
DisplayName : "aaa" ,
} ,
{
Slug : "aaa-code-server" ,
Order : 4 ,
} ,
{
Slug : "bbb-code-server" ,
Order : 3 ,
} ,
{
Slug : "bbb" ,
} ,
}
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , & echo . Responses {
Parse : echo . ParseComplete ,
ProvisionApply : [ ] * proto . Response { {
Type : & proto . Response_Apply {
Apply : & proto . ApplyComplete {
Resources : [ ] * proto . Resource { {
Name : "some" ,
Type : "example" ,
Agents : [ ] * proto . Agent { {
Id : "something" ,
Auth : & proto . Agent_Token { } ,
Apps : apps ,
} } ,
} } ,
} ,
} ,
} } ,
} )
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
workspace , err := client . Workspace ( ctx , workspace . ID )
require . NoError ( t , err )
require . Len ( t , workspace . LatestBuild . Resources [ 0 ] . Agents , 1 )
agent := workspace . LatestBuild . Resources [ 0 ] . Agents [ 0 ]
require . Len ( t , agent . Apps , 4 )
require . Equal ( t , "bbb" , agent . Apps [ 0 ] . Slug ) // empty-display-name < "aaa"
require . Equal ( t , "aaa" , agent . Apps [ 1 ] . Slug ) // no order < any order
require . Equal ( t , "bbb-code-server" , agent . Apps [ 2 ] . Slug ) // order = 3 < order = 4
require . Equal ( t , "aaa-code-server" , agent . Apps [ 3 ] . Slug )
} )
2022-10-03 21:01:13 +00:00
t . Run ( "Metadata" , func ( t * testing . T ) {
t . Parallel ( )
client := coderdtest . New ( t , & coderdtest . Options {
IncludeProvisionerDaemon : true ,
} )
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , & echo . Responses {
Parse : echo . ParseComplete ,
2023-08-25 06:10:15 +00:00
ProvisionApply : [ ] * proto . Response { {
Type : & proto . Response_Apply {
Apply : & proto . ApplyComplete {
2022-10-03 21:01:13 +00:00
Resources : [ ] * proto . Resource { {
Name : "some" ,
Type : "example" ,
Agents : [ ] * proto . Agent { {
Id : "something" ,
Auth : & proto . Agent_Token { } ,
} } ,
Metadata : [ ] * proto . Resource_Metadata { {
Key : "foo" ,
Value : "bar" ,
} , {
Key : "null" ,
IsNull : true ,
} , {
Key : "empty" ,
} , {
Key : "secret" ,
Value : "squirrel" ,
Sensitive : true ,
} } ,
} } ,
} ,
} ,
} } ,
} )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2022-10-03 21:01:13 +00:00
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2022-10-03 21:01:13 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
workspace , err := client . Workspace ( ctx , workspace . ID )
require . NoError ( t , err )
metadata := workspace . LatestBuild . Resources [ 0 ] . Metadata
require . Equal ( t , [ ] codersdk . WorkspaceResourceMetadata { {
Key : "foo" ,
Value : "bar" ,
2022-12-14 19:08:22 +00:00
} , {
Key : "empty" ,
2022-10-03 21:01:13 +00:00
} , {
Key : "secret" ,
Value : "squirrel" ,
Sensitive : true ,
} } , metadata )
} )
}
2023-01-17 15:24:45 +00:00
func TestWorkspaceWithRichParameters ( t * testing . T ) {
t . Parallel ( )
const (
firstParameterName = "first_parameter"
2023-02-07 08:36:13 +00:00
firstParameterType = "string"
2023-02-08 11:57:12 +00:00
firstParameterDescription = "This is _first_ *parameter*"
2023-01-17 15:24:45 +00:00
firstParameterValue = "1"
2023-02-07 08:36:13 +00:00
secondParameterName = "second_parameter"
2023-04-03 12:37:47 +00:00
secondParameterDisplayName = "Second Parameter"
2023-02-07 08:36:13 +00:00
secondParameterType = "number"
2023-02-08 11:57:12 +00:00
secondParameterDescription = "_This_ is second *parameter*"
2023-02-07 08:36:13 +00:00
secondParameterValue = "2"
secondParameterValidationMonotonic = codersdk . MonotonicOrderIncreasing
2023-01-17 15:24:45 +00:00
)
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , & echo . Responses {
Parse : echo . ParseComplete ,
2023-08-25 06:10:15 +00:00
ProvisionPlan : [ ] * proto . Response {
2023-01-17 15:24:45 +00:00
{
2023-08-25 06:10:15 +00:00
Type : & proto . Response_Plan {
Plan : & proto . PlanComplete {
2023-01-17 15:24:45 +00:00
Parameters : [ ] * proto . RichParameter {
2023-02-07 08:36:13 +00:00
{
Name : firstParameterName ,
Type : firstParameterType ,
Description : firstParameterDescription ,
} ,
{
Name : secondParameterName ,
2023-04-03 12:37:47 +00:00
DisplayName : secondParameterDisplayName ,
2023-02-07 08:36:13 +00:00
Type : secondParameterType ,
Description : secondParameterDescription ,
2023-05-30 12:57:06 +00:00
ValidationMin : ptr . Ref ( int32 ( 1 ) ) ,
ValidationMax : ptr . Ref ( int32 ( 3 ) ) ,
2023-02-07 08:36:13 +00:00
ValidationMonotonic : string ( secondParameterValidationMonotonic ) ,
} ,
2023-01-17 15:24:45 +00:00
} ,
} ,
} ,
2023-02-19 00:32:09 +00:00
} ,
} ,
2023-08-25 06:10:15 +00:00
ProvisionApply : [ ] * proto . Response { {
Type : & proto . Response_Apply {
Apply : & proto . ApplyComplete { } ,
2023-01-17 15:24:45 +00:00
} ,
} } ,
} )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2023-01-17 15:24:45 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2023-02-08 11:57:12 +00:00
firstParameterDescriptionPlaintext , err := parameter . Plaintext ( firstParameterDescription )
require . NoError ( t , err )
secondParameterDescriptionPlaintext , err := parameter . Plaintext ( secondParameterDescription )
require . NoError ( t , err )
2023-01-17 15:24:45 +00:00
templateRichParameters , err := client . TemplateVersionRichParameters ( ctx , version . ID )
require . NoError ( t , err )
require . Len ( t , templateRichParameters , 2 )
2023-02-08 11:57:12 +00:00
require . Equal ( t , firstParameterName , templateRichParameters [ 0 ] . Name )
require . Equal ( t , firstParameterType , templateRichParameters [ 0 ] . Type )
require . Equal ( t , firstParameterDescription , templateRichParameters [ 0 ] . Description )
require . Equal ( t , firstParameterDescriptionPlaintext , templateRichParameters [ 0 ] . DescriptionPlaintext )
require . Equal ( t , codersdk . ValidationMonotonicOrder ( "" ) , templateRichParameters [ 0 ] . ValidationMonotonic ) // no validation for string
require . Equal ( t , secondParameterName , templateRichParameters [ 1 ] . Name )
2023-04-03 12:37:47 +00:00
require . Equal ( t , secondParameterDisplayName , templateRichParameters [ 1 ] . DisplayName )
2023-02-08 11:57:12 +00:00
require . Equal ( t , secondParameterType , templateRichParameters [ 1 ] . Type )
require . Equal ( t , secondParameterDescription , templateRichParameters [ 1 ] . Description )
require . Equal ( t , secondParameterDescriptionPlaintext , templateRichParameters [ 1 ] . DescriptionPlaintext )
require . Equal ( t , secondParameterValidationMonotonic , templateRichParameters [ 1 ] . ValidationMonotonic )
2023-01-17 15:24:45 +00:00
expectedBuildParameters := [ ] codersdk . WorkspaceBuildParameter {
{ Name : firstParameterName , Value : firstParameterValue } ,
{ Name : secondParameterName , Value : secondParameterValue } ,
}
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID , func ( cwr * codersdk . CreateWorkspaceRequest ) {
cwr . RichParameterValues = expectedBuildParameters
} )
2023-10-03 17:02:56 +00:00
workspaceBuild := coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2023-01-17 15:24:45 +00:00
require . Equal ( t , codersdk . WorkspaceStatusRunning , workspaceBuild . Status )
workspaceBuildParameters , err := client . WorkspaceBuildParameters ( ctx , workspaceBuild . ID )
require . NoError ( t , err )
require . ElementsMatch ( t , expectedBuildParameters , workspaceBuildParameters )
}
2023-03-07 15:38:31 +00:00
func TestWorkspaceWithOptionalRichParameters ( t * testing . T ) {
t . Parallel ( )
const (
firstParameterName = "first_parameter"
firstParameterType = "string"
firstParameterDescription = "This is _first_ *parameter*"
firstParameterDefaultValue = "1"
secondParameterName = "second_parameter"
secondParameterType = "number"
secondParameterDescription = "_This_ is second *parameter*"
secondParameterRequired = true
secondParameterValue = "333"
)
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , & echo . Responses {
Parse : echo . ParseComplete ,
2023-08-25 06:10:15 +00:00
ProvisionPlan : [ ] * proto . Response {
2023-03-07 15:38:31 +00:00
{
2023-08-25 06:10:15 +00:00
Type : & proto . Response_Plan {
Plan : & proto . PlanComplete {
2023-03-07 15:38:31 +00:00
Parameters : [ ] * proto . RichParameter {
{
Name : firstParameterName ,
Type : firstParameterType ,
Description : firstParameterDescription ,
DefaultValue : firstParameterDefaultValue ,
} ,
{
Name : secondParameterName ,
Type : secondParameterType ,
Description : secondParameterDescription ,
Required : secondParameterRequired ,
} ,
} ,
} ,
} ,
} ,
} ,
2023-08-25 06:10:15 +00:00
ProvisionApply : [ ] * proto . Response { {
Type : & proto . Response_Apply {
Apply : & proto . ApplyComplete { } ,
2023-03-07 15:38:31 +00:00
} ,
} } ,
} )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2023-03-07 15:38:31 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
templateRichParameters , err := client . TemplateVersionRichParameters ( ctx , version . ID )
require . NoError ( t , err )
require . Len ( t , templateRichParameters , 2 )
require . Equal ( t , firstParameterName , templateRichParameters [ 0 ] . Name )
require . Equal ( t , firstParameterType , templateRichParameters [ 0 ] . Type )
require . Equal ( t , firstParameterDescription , templateRichParameters [ 0 ] . Description )
require . Equal ( t , firstParameterDefaultValue , templateRichParameters [ 0 ] . DefaultValue )
require . Equal ( t , secondParameterName , templateRichParameters [ 1 ] . Name )
require . Equal ( t , secondParameterType , templateRichParameters [ 1 ] . Type )
require . Equal ( t , secondParameterDescription , templateRichParameters [ 1 ] . Description )
require . Equal ( t , secondParameterRequired , templateRichParameters [ 1 ] . Required )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID , func ( cwr * codersdk . CreateWorkspaceRequest ) {
2023-05-23 08:06:33 +00:00
cwr . RichParameterValues = [ ] codersdk . WorkspaceBuildParameter {
// First parameter is optional, so coder will pick the default value.
{ Name : secondParameterName , Value : secondParameterValue } ,
}
2023-03-07 15:38:31 +00:00
} )
2023-10-03 17:02:56 +00:00
workspaceBuild := coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2023-03-07 15:38:31 +00:00
require . Equal ( t , codersdk . WorkspaceStatusRunning , workspaceBuild . Status )
workspaceBuildParameters , err := client . WorkspaceBuildParameters ( ctx , workspaceBuild . ID )
require . NoError ( t , err )
2023-05-23 08:06:33 +00:00
expectedBuildParameters := [ ] codersdk . WorkspaceBuildParameter {
// Coderd inserts the default for the missing parameter
{ Name : firstParameterName , Value : firstParameterDefaultValue } ,
{ Name : secondParameterName , Value : secondParameterValue } ,
}
2023-03-07 15:38:31 +00:00
require . ElementsMatch ( t , expectedBuildParameters , workspaceBuildParameters )
}
2023-06-28 21:12:49 +00:00
2023-07-10 11:44:03 +00:00
func TestWorkspaceWithEphemeralRichParameters ( t * testing . T ) {
t . Parallel ( )
const (
firstParameterName = "first_parameter"
firstParameterType = "string"
firstParameterDescription = "This is first parameter"
firstParameterMutable = true
firstParameterDefaultValue = "1"
firstParameterValue = "i_am_first_parameter"
ephemeralParameterName = "second_parameter"
ephemeralParameterType = "string"
ephemeralParameterDescription = "This is second parameter"
ephemeralParameterDefaultValue = ""
ephemeralParameterMutable = true
ephemeralParameterValue = "i_am_ephemeral"
)
// Create template version with ephemeral parameter
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , & echo . Responses {
Parse : echo . ParseComplete ,
2023-08-25 06:10:15 +00:00
ProvisionPlan : [ ] * proto . Response {
2023-07-10 11:44:03 +00:00
{
2023-08-25 06:10:15 +00:00
Type : & proto . Response_Plan {
Plan : & proto . PlanComplete {
2023-07-10 11:44:03 +00:00
Parameters : [ ] * proto . RichParameter {
{
Name : firstParameterName ,
Type : firstParameterType ,
Description : firstParameterDescription ,
DefaultValue : firstParameterDefaultValue ,
Mutable : firstParameterMutable ,
} ,
{
Name : ephemeralParameterName ,
Type : ephemeralParameterType ,
Description : ephemeralParameterDescription ,
DefaultValue : ephemeralParameterDefaultValue ,
Mutable : ephemeralParameterMutable ,
Ephemeral : true ,
} ,
} ,
} ,
} ,
} ,
} ,
2023-08-25 06:10:15 +00:00
ProvisionApply : [ ] * proto . Response { {
Type : & proto . Response_Apply {
Apply : & proto . ApplyComplete { } ,
2023-07-10 11:44:03 +00:00
} ,
} } ,
} )
2023-10-03 17:02:56 +00:00
coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2023-07-10 11:44:03 +00:00
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
// Create workspace with default values
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2023-10-03 17:02:56 +00:00
workspaceBuild := coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2023-07-10 11:44:03 +00:00
require . Equal ( t , codersdk . WorkspaceStatusRunning , workspaceBuild . Status )
// Verify workspace build parameters (default values)
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
workspaceBuildParameters , err := client . WorkspaceBuildParameters ( ctx , workspaceBuild . ID )
require . NoError ( t , err )
expectedBuildParameters := [ ] codersdk . WorkspaceBuildParameter {
{ Name : firstParameterName , Value : firstParameterDefaultValue } ,
{ Name : ephemeralParameterName , Value : ephemeralParameterDefaultValue } ,
}
require . ElementsMatch ( t , expectedBuildParameters , workspaceBuildParameters )
// Trigger workspace build job with ephemeral parameter
workspaceBuild , err = client . CreateWorkspaceBuild ( ctx , workspaceBuild . WorkspaceID , codersdk . CreateWorkspaceBuildRequest {
Transition : codersdk . WorkspaceTransitionStart ,
RichParameterValues : [ ] codersdk . WorkspaceBuildParameter {
{
Name : ephemeralParameterName ,
Value : ephemeralParameterValue ,
} ,
} ,
} )
require . NoError ( t , err )
2023-10-03 17:02:56 +00:00
workspaceBuild = coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspaceBuild . ID )
2023-07-10 11:44:03 +00:00
require . Equal ( t , codersdk . WorkspaceStatusRunning , workspaceBuild . Status )
// Verify workspace build parameters (including ephemeral)
workspaceBuildParameters , err = client . WorkspaceBuildParameters ( ctx , workspaceBuild . ID )
require . NoError ( t , err )
expectedBuildParameters = [ ] codersdk . WorkspaceBuildParameter {
{ Name : firstParameterName , Value : firstParameterDefaultValue } ,
{ Name : ephemeralParameterName , Value : ephemeralParameterValue } ,
}
require . ElementsMatch ( t , expectedBuildParameters , workspaceBuildParameters )
// Trigger workspace build one more time without the ephemeral parameter
workspaceBuild , err = client . CreateWorkspaceBuild ( ctx , workspaceBuild . WorkspaceID , codersdk . CreateWorkspaceBuildRequest {
Transition : codersdk . WorkspaceTransitionStart ,
RichParameterValues : [ ] codersdk . WorkspaceBuildParameter {
{
Name : firstParameterName ,
Value : firstParameterValue ,
} ,
} ,
} )
require . NoError ( t , err )
2023-10-03 17:02:56 +00:00
workspaceBuild = coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspaceBuild . ID )
2023-07-10 11:44:03 +00:00
require . Equal ( t , codersdk . WorkspaceStatusRunning , workspaceBuild . Status )
// Verify workspace build parameters (ephemeral should be back to default)
workspaceBuildParameters , err = client . WorkspaceBuildParameters ( ctx , workspaceBuild . ID )
require . NoError ( t , err )
expectedBuildParameters = [ ] codersdk . WorkspaceBuildParameter {
{ Name : firstParameterName , Value : firstParameterValue } ,
{ Name : ephemeralParameterName , Value : ephemeralParameterDefaultValue } ,
}
require . ElementsMatch ( t , expectedBuildParameters , workspaceBuildParameters )
}
2023-08-24 18:25:54 +00:00
func TestWorkspaceDormant ( t * testing . T ) {
2023-06-28 21:12:49 +00:00
t . Parallel ( )
t . Run ( "OK" , func ( t * testing . T ) {
t . Parallel ( )
var (
2023-10-05 18:41:07 +00:00
auditRecorder = audit . NewMock ( )
client = coderdtest . New ( t , & coderdtest . Options {
IncludeProvisionerDaemon : true ,
Auditor : auditRecorder ,
} )
2023-08-24 18:25:54 +00:00
user = coderdtest . CreateFirstUser ( t , client )
version = coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
2023-10-03 17:02:56 +00:00
_ = coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2023-08-24 18:25:54 +00:00
timeTilDormantAutoDelete = time . Minute
2023-06-28 21:12:49 +00:00
)
2023-07-21 03:01:11 +00:00
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID , func ( ctr * codersdk . CreateTemplateRequest ) {
2023-08-24 18:25:54 +00:00
ctr . TimeTilDormantAutoDeleteMillis = ptr . Ref [ int64 ] ( timeTilDormantAutoDelete . Milliseconds ( ) )
2023-07-21 03:01:11 +00:00
} )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2023-10-03 17:02:56 +00:00
_ = coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2023-07-21 03:01:11 +00:00
2023-06-28 21:12:49 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2023-07-21 03:01:11 +00:00
lastUsedAt := workspace . LastUsedAt
2023-10-05 18:41:07 +00:00
auditRecorder . ResetLogs ( )
2023-08-24 18:25:54 +00:00
err := client . UpdateWorkspaceDormancy ( ctx , workspace . ID , codersdk . UpdateWorkspaceDormancy {
Dormant : true ,
2023-06-28 21:12:49 +00:00
} )
require . NoError ( t , err )
2023-11-06 09:17:07 +00:00
require . True ( t , auditRecorder . Contains ( t , database . AuditLog {
Action : database . AuditActionWrite ,
ResourceType : database . ResourceTypeWorkspace ,
ResourceTarget : workspace . Name ,
} ) )
2023-06-28 21:12:49 +00:00
2023-07-03 02:29:52 +00:00
workspace = coderdtest . MustWorkspace ( t , client , workspace . ID )
2023-06-28 21:12:49 +00:00
require . NoError ( t , err , "fetch provisioned workspace" )
2023-08-24 18:25:54 +00:00
// The template doesn't have a time_til_dormant_autodelete set so this should be nil.
2023-07-21 03:01:11 +00:00
require . Nil ( t , workspace . DeletingAt )
2023-08-24 18:25:54 +00:00
require . NotNil ( t , workspace . DormantAt )
require . WithinRange ( t , * workspace . DormantAt , time . Now ( ) . Add ( - time . Second * 10 ) , time . Now ( ) )
2023-07-21 03:01:11 +00:00
require . Equal ( t , lastUsedAt , workspace . LastUsedAt )
2023-06-28 21:12:49 +00:00
2023-07-21 03:01:11 +00:00
workspace = coderdtest . MustWorkspace ( t , client , workspace . ID )
lastUsedAt = workspace . LastUsedAt
2023-08-24 18:25:54 +00:00
err = client . UpdateWorkspaceDormancy ( ctx , workspace . ID , codersdk . UpdateWorkspaceDormancy {
Dormant : false ,
2023-06-28 21:12:49 +00:00
} )
require . NoError ( t , err )
workspace , err = client . Workspace ( ctx , workspace . ID )
require . NoError ( t , err , "fetch provisioned workspace" )
2023-08-24 18:25:54 +00:00
require . Nil ( t , workspace . DormantAt )
// The template doesn't have a time_til_dormant_autodelete set so this should be nil.
2023-07-21 03:01:11 +00:00
require . Nil ( t , workspace . DeletingAt )
2023-08-24 18:25:54 +00:00
// The last_used_at should get updated when we activate the workspace.
2023-07-03 02:29:52 +00:00
require . True ( t , workspace . LastUsedAt . After ( lastUsedAt ) )
2023-06-28 21:12:49 +00:00
} )
t . Run ( "CannotStart" , func ( t * testing . T ) {
t . Parallel ( )
var (
client = coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
user = coderdtest . CreateFirstUser ( t , client )
version = coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
2023-10-03 17:02:56 +00:00
_ = coderdtest . AwaitTemplateVersionJobCompleted ( t , client , version . ID )
2023-06-28 21:12:49 +00:00
template = coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
workspace = coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2023-10-03 17:02:56 +00:00
_ = coderdtest . AwaitWorkspaceBuildJobCompleted ( t , client , workspace . LatestBuild . ID )
2023-06-28 21:12:49 +00:00
)
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
2023-08-24 18:25:54 +00:00
err := client . UpdateWorkspaceDormancy ( ctx , workspace . ID , codersdk . UpdateWorkspaceDormancy {
Dormant : true ,
2023-06-28 21:12:49 +00:00
} )
require . NoError ( t , err )
2023-08-24 18:25:54 +00:00
// Should be able to stop a workspace while it is dormant.
2023-06-28 21:12:49 +00:00
coderdtest . MustTransitionWorkspace ( t , client , workspace . ID , database . WorkspaceTransitionStart , database . WorkspaceTransitionStop )
2023-08-24 18:25:54 +00:00
// Should not be able to start a workspace while it is dormant.
2023-06-28 21:12:49 +00:00
_ , err = client . CreateWorkspaceBuild ( ctx , workspace . ID , codersdk . CreateWorkspaceBuildRequest {
TemplateVersionID : template . ActiveVersionID ,
Transition : codersdk . WorkspaceTransition ( database . WorkspaceTransitionStart ) ,
} )
require . Error ( t , err )
2023-08-24 18:25:54 +00:00
err = client . UpdateWorkspaceDormancy ( ctx , workspace . ID , codersdk . UpdateWorkspaceDormancy {
Dormant : false ,
2023-06-28 21:12:49 +00:00
} )
require . NoError ( t , err )
coderdtest . MustTransitionWorkspace ( t , client , workspace . ID , database . WorkspaceTransitionStop , database . WorkspaceTransitionStart )
} )
}
2024-01-24 13:39:19 +00:00
func TestWorkspaceFavoriteUnfavorite ( t * testing . T ) {
t . Parallel ( )
// Given:
var (
auditRecorder = audit . NewMock ( )
client , db = coderdtest . NewWithDatabase ( t , & coderdtest . Options {
Auditor : auditRecorder ,
} )
owner = coderdtest . CreateFirstUser ( t , client )
memberClient , member = coderdtest . CreateAnotherUser ( t , client , owner . OrganizationID )
// This will be our 'favorite' workspace
wsb1 = dbfake . WorkspaceBuild ( t , db , database . Workspace { OwnerID : member . ID , OrganizationID : owner . OrganizationID } ) . Do ( )
wsb2 = dbfake . WorkspaceBuild ( t , db , database . Workspace { OwnerID : owner . UserID , OrganizationID : owner . OrganizationID } ) . Do ( )
)
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
// Initially, workspace should not be favored for member.
ws , err := memberClient . Workspace ( ctx , wsb1 . Workspace . ID )
require . NoError ( t , err )
require . False ( t , ws . Favorite )
// When user favorites workspace
err = memberClient . FavoriteWorkspace ( ctx , wsb1 . Workspace . ID )
require . NoError ( t , err )
// Then it should be favored for them.
ws , err = memberClient . Workspace ( ctx , wsb1 . Workspace . ID )
require . NoError ( t , err )
require . True ( t , ws . Favorite )
// And it should be audited.
require . True ( t , auditRecorder . Contains ( t , database . AuditLog {
Action : database . AuditActionWrite ,
ResourceType : database . ResourceTypeWorkspace ,
ResourceTarget : wsb1 . Workspace . Name ,
UserID : member . ID ,
} ) )
auditRecorder . ResetLogs ( )
// This should not show for the owner.
ws , err = client . Workspace ( ctx , wsb1 . Workspace . ID )
require . NoError ( t , err )
require . False ( t , ws . Favorite )
// When member unfavorites workspace
err = memberClient . UnfavoriteWorkspace ( ctx , wsb1 . Workspace . ID )
require . NoError ( t , err )
// Then it should no longer be favored
ws , err = memberClient . Workspace ( ctx , wsb1 . Workspace . ID )
require . NoError ( t , err )
require . False ( t , ws . Favorite , "no longer favorite" )
// And it should show in the audit logs.
require . True ( t , auditRecorder . Contains ( t , database . AuditLog {
Action : database . AuditActionWrite ,
ResourceType : database . ResourceTypeWorkspace ,
ResourceTarget : wsb1 . Workspace . Name ,
UserID : member . ID ,
} ) )
// Users without write access to the workspace should not be able to perform the above.
err = memberClient . FavoriteWorkspace ( ctx , wsb2 . Workspace . ID )
var sdkErr * codersdk . Error
require . ErrorAs ( t , err , & sdkErr )
require . Equal ( t , http . StatusNotFound , sdkErr . StatusCode ( ) )
err = memberClient . UnfavoriteWorkspace ( ctx , wsb2 . Workspace . ID )
require . ErrorAs ( t , err , & sdkErr )
require . Equal ( t , http . StatusNotFound , sdkErr . StatusCode ( ) )
// You should not be able to favorite any workspace you do not own, even if you are the owner.
err = client . FavoriteWorkspace ( ctx , wsb1 . Workspace . ID )
require . ErrorAs ( t , err , & sdkErr )
require . Equal ( t , http . StatusForbidden , sdkErr . StatusCode ( ) )
err = client . UnfavoriteWorkspace ( ctx , wsb1 . Workspace . ID )
require . ErrorAs ( t , err , & sdkErr )
require . Equal ( t , http . StatusForbidden , sdkErr . StatusCode ( ) )
}