2022-01-25 19:52:58 +00:00
package coderd_test
import (
"context"
2022-06-14 16:21:30 +00:00
"fmt"
2022-02-06 00:24:51 +00:00
"net/http"
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"
"github.com/coder/coder/agent"
2022-09-10 16:07:45 +00:00
"github.com/coder/coder/coderd/audit"
2022-01-25 19:52:58 +00:00
"github.com/coder/coder/coderd/coderdtest"
2022-09-10 16:07:45 +00:00
"github.com/coder/coder/coderd/database"
2023-02-08 11:57:12 +00:00
"github.com/coder/coder/coderd/parameter"
2022-06-14 13:46:33 +00:00
"github.com/coder/coder/coderd/rbac"
2023-03-07 14:14:58 +00:00
"github.com/coder/coder/coderd/schedule"
2022-06-14 13:46:33 +00:00
"github.com/coder/coder/coderd/util/ptr"
2022-01-25 19:52:58 +00:00
"github.com/coder/coder/codersdk"
2023-01-29 21:47:24 +00:00
"github.com/coder/coder/codersdk/agentsdk"
2022-06-14 13:46:33 +00:00
"github.com/coder/coder/cryptorand"
2022-03-07 17:40:54 +00:00
"github.com/coder/coder/provisioner/echo"
"github.com/coder/coder/provisionersdk/proto"
2022-08-09 17:17:00 +00:00
"github.com/coder/coder/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 )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
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 )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
coderdtest . AwaitWorkspaceBuildJob ( t , client , workspace . LatestBuild . ID )
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" )
coderdtest . AwaitWorkspaceBuildJob ( t , client , build . ID )
// 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 ( )
2022-09-04 16:28:09 +00:00
client := coderdtest . New ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
2022-08-26 09:28:38 +00:00
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
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 )
coderdtest . AwaitWorkspaceBuildJob ( t , client , ws1 . LatestBuild . ID )
coderdtest . AwaitWorkspaceBuildJob ( t , client , ws2 . LatestBuild . ID )
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitMedium )
defer cancel ( )
want := ws1 . Name + "-test"
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
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 )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
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
} )
2022-05-19 14:29:10 +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 )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
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-05-18 23:15:19 +00:00
coderdtest . AwaitWorkspaceBuildJob ( 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
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 )
require . Equal ( t , http . StatusUnauthorized , apiErr . StatusCode ( ) )
} )
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 )
coderdtest . AwaitTemplateVersionJob ( t , client , 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-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 ( ) )
} )
t . Run ( "Create" , func ( t * testing . T ) {
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 )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
2023-03-16 17:47:54 +00:00
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
coderdtest . AwaitWorkspaceBuildJob ( t , client , workspace . LatestBuild . ID )
2022-09-10 16:07:45 +00:00
2023-03-21 18:03:38 +00:00
require . Eventually ( t , func ( ) bool {
2023-03-30 17:13:03 +00:00
if len ( auditor . AuditLogs ( ) ) < 6 {
2023-03-21 18:03:38 +00:00
return false
}
2023-03-30 17:13:03 +00:00
return auditor . AuditLogs ( ) [ 4 ] . Action == database . AuditActionCreate
2023-03-21 18:03:38 +00:00
} , testutil . WaitMedium , testutil . IntervalFast )
2022-05-16 19:36:27 +00:00
} )
2022-05-30 19:19:17 +00:00
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 )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
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 )
2022-08-24 14:45:14 +00:00
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
// 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 ) )
} )
// 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
} )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID , func ( cwr * codersdk . CreateWorkspaceRequest ) {
cwr . TTLMillis = nil // ensure that no default TTL is set
} )
// TTL should be set by the template
2022-11-09 19:36:25 +00:00
require . Equal ( t , template . DefaultTTLMillis , templateTTL )
2023-03-07 14:14:58 +00:00
require . Equal ( t , template . DefaultTTLMillis , * 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 )
coderdtest . AwaitTemplateVersionJob ( 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 )
require . Equal ( t , apiErr . Validations [ 0 ] . Field , "ttl_ms" )
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
} )
2022-06-07 12:37:45 +00:00
coderdtest . AwaitTemplateVersionJob ( 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 )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
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 )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
coderdtest . AwaitWorkspaceBuildJob ( t , client , workspace . LatestBuild . ID )
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" )
coderdtest . AwaitWorkspaceBuildJob ( t , client , build . ID )
// 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 ,
} )
require . NoError ( t , err )
coderdtest . AwaitWorkspaceBuildJob ( t , client , workspace . LatestBuild . ID )
// 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" )
coderdtest . AwaitWorkspaceBuildJob ( t , client , build . ID )
// 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
}
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
coderdtest . AwaitTemplateVersionJob ( 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 {
latest := coderdtest . AwaitWorkspaceBuildJob ( t , client , w . Workspace . LatestBuild . ID )
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 ( )
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 )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
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
} )
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 )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
template2 := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
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 )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
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"
_ = coderdtest . AwaitWorkspaceBuildJob ( t , client , workspace1 . LatestBuild . ID )
_ = coderdtest . AwaitWorkspaceBuildJob ( t , client , workspace2 . LatestBuild . ID )
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 )
_ = coderdtest . AwaitWorkspaceBuildJob ( t , client , build1 . ID )
// 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 )
_ = coderdtest . AwaitWorkspaceBuildJob ( t , client , build2 . ID )
// 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 )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
template2 := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
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 ,
ProvisionPlan : echo . ProvisionComplete ,
ProvisionApply : echo . ProvisionApplyWithAgent ( authToken ) ,
2022-11-24 14:33:13 +00:00
} )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
coderdtest . AwaitWorkspaceBuildJob ( t , client , workspace . LatestBuild . ID )
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 ,
ProvisionPlan : echo . ProvisionComplete ,
ProvisionApply : echo . ProvisionApplyWithAgent ( authToken ) ,
2022-11-24 14:33:13 +00:00
} )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
coderdtest . AwaitWorkspaceBuildJob ( t , client , workspace . LatestBuild . ID )
2023-01-29 21:47:24 +00:00
agentClient := agentsdk . New ( client . URL )
2022-11-24 14:33:13 +00:00
agentClient . SetSessionToken ( authToken )
agentCloser := agent . New ( agent . Options {
Client : agentClient ,
Logger : slogtest . Make ( t , nil ) . Named ( "agent" ) . Leveled ( slog . LevelDebug ) ,
} )
defer func ( ) {
_ = agentCloser . Close ( )
} ( )
coderdtest . AwaitWorkspaceAgents ( t , client , workspace . ID )
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 ,
ProvisionPlan : echo . ProvisionComplete ,
ProvisionApply : [ ] * proto . Provision_Response { {
Type : & proto . Provision_Response_Complete {
Complete : & proto . Provision_Complete {
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 )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
coderdtest . AwaitWorkspaceBuildJob ( t , client , workspace . LatestBuild . ID )
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" )
} )
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 )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
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 )
// empty finds all workspaces
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
// offset 1 finds 2 workspaces
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
// offset 1 limit 1 finds 1 workspace
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
// offset 3 finds no workspaces
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 )
2022-10-20 17:23:14 +00:00
}
2022-03-07 17:40:54 +00:00
func TestPostWorkspaceBuild ( t * testing . T ) {
2022-02-06 00:24:51 +00:00
t . Parallel ( )
2022-04-06 17:42:40 +00:00
t . Run ( "NoTemplateVersion" , func ( 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 )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
coderdtest . AwaitTemplateVersionJob ( t , client , 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 . CreateWorkspaceBuild ( ctx , workspace . ID , codersdk . CreateWorkspaceBuildRequest {
2022-04-06 17:42:40 +00:00
TemplateVersionID : uuid . New ( ) ,
2022-05-19 18:04:44 +00:00
Transition : codersdk . WorkspaceTransitionStart ,
2022-01-25 19:52:58 +00:00
} )
2022-02-06 00:24:51 +00:00
require . Error ( t , err )
var apiErr * codersdk . Error
require . ErrorAs ( t , err , & apiErr )
require . Equal ( t , http . StatusBadRequest , apiErr . StatusCode ( ) )
2022-01-25 19:52:58 +00:00
} )
2022-04-06 17:42:40 +00:00
t . Run ( "TemplateVersionFailedImport" , func ( 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 , & echo . Responses {
2022-11-11 22:45:58 +00:00
ProvisionApply : [ ] * proto . Provision_Response { { } } ,
2022-01-25 19:52:58 +00:00
} )
2022-04-06 17:42:40 +00:00
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
coderdtest . AwaitTemplateVersionJob ( t , client , version . 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-04-06 17:42:40 +00:00
TemplateID : template . ID ,
Name : "workspace" ,
2022-01-25 19:52:58 +00:00
} )
2022-02-06 00:24:51 +00:00
var apiErr * codersdk . Error
require . ErrorAs ( t , err , & apiErr )
2023-01-13 14:30:48 +00:00
require . Equal ( t , http . StatusBadRequest , apiErr . StatusCode ( ) )
2022-01-25 19:52:58 +00:00
} )
2022-03-07 17:40:54 +00:00
t . Run ( "AlreadyActive" , func ( t * testing . T ) {
2022-01-25 19:52:58 +00:00
t . Parallel ( )
2022-06-27 18:50:52 +00:00
client , closer := coderdtest . NewWithProvisionerCloser ( t , nil )
defer closer . Close ( )
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 )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
2022-06-27 18:50:52 +00:00
closer . Close ( )
2022-03-07 17:40:54 +00:00
// Close here so workspace build doesn't process!
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 . CreateWorkspaceBuild ( ctx , workspace . ID , codersdk . CreateWorkspaceBuildRequest {
2022-04-06 17:42:40 +00:00
TemplateVersionID : template . ActiveVersionID ,
2022-05-19 18:04:44 +00:00
Transition : codersdk . WorkspaceTransitionStart ,
2022-01-25 19:52:58 +00:00
} )
require . Error ( t , err )
2022-02-06 00:24:51 +00:00
var apiErr * codersdk . Error
require . ErrorAs ( t , err , & apiErr )
require . Equal ( t , http . StatusConflict , apiErr . StatusCode ( ) )
2022-01-25 19:52:58 +00:00
} )
2022-05-18 16:33:33 +00:00
t . Run ( "IncrementBuildNumber" , func ( 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 )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
2022-04-25 21:11:03 +00:00
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2022-03-22 19:17:50 +00:00
coderdtest . AwaitWorkspaceBuildJob ( t , client , workspace . LatestBuild . ID )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
build , err := client . CreateWorkspaceBuild ( ctx , workspace . ID , codersdk . CreateWorkspaceBuildRequest {
2022-04-06 17:42:40 +00:00
TemplateVersionID : template . ActiveVersionID ,
2022-05-19 18:04:44 +00:00
Transition : codersdk . WorkspaceTransitionStart ,
2022-03-07 17:40:54 +00:00
} )
require . NoError ( t , err )
2022-05-18 16:33:33 +00:00
require . Equal ( t , workspace . LatestBuild . BuildNumber + 1 , build . BuildNumber )
2022-01-25 19:52:58 +00:00
} )
2022-05-02 22:51:58 +00:00
t . Run ( "WithState" , func ( t * testing . T ) {
t . Parallel ( )
2022-09-18 21:40:24 +00:00
client , closeDaemon := coderdtest . NewWithProvisionerCloser ( t , & coderdtest . Options {
2022-09-04 16:28:09 +00:00
IncludeProvisionerDaemon : true ,
2022-06-27 18:50:52 +00:00
} )
2022-05-02 22:51:58 +00:00
user := coderdtest . CreateFirstUser ( t , client )
version := coderdtest . CreateTemplateVersion ( t , client , user . OrganizationID , nil )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
coderdtest . AwaitWorkspaceBuildJob ( t , client , workspace . LatestBuild . ID )
wantState := [ ] byte ( "something" )
2022-09-18 21:40:24 +00:00
_ = closeDaemon . Close ( )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
build , err := client . CreateWorkspaceBuild ( ctx , workspace . ID , codersdk . CreateWorkspaceBuildRequest {
2022-05-02 22:51:58 +00:00
TemplateVersionID : template . ActiveVersionID ,
2022-05-19 18:04:44 +00:00
Transition : codersdk . WorkspaceTransitionStart ,
2022-05-02 22:51:58 +00:00
ProvisionerState : wantState ,
} )
require . NoError ( t , err )
2022-08-09 17:17:00 +00:00
gotState , err := client . WorkspaceBuildState ( ctx , build . ID )
2022-05-02 22:51:58 +00:00
require . NoError ( t , err )
require . Equal ( t , wantState , gotState )
} )
2022-03-22 19:17:50 +00:00
t . Run ( "Delete" , func ( t * testing . T ) {
2022-03-07 17:40:54 +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 )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
2022-04-25 21:11:03 +00:00
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2022-03-22 19:17:50 +00:00
coderdtest . AwaitWorkspaceBuildJob ( t , client , workspace . LatestBuild . ID )
2022-08-09 17:17:00 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , testutil . WaitLong )
defer cancel ( )
build , err := client . CreateWorkspaceBuild ( ctx , workspace . ID , codersdk . CreateWorkspaceBuildRequest {
2022-05-19 18:04:44 +00:00
Transition : codersdk . WorkspaceTransitionDelete ,
2022-03-07 17:40:54 +00:00
} )
require . NoError ( t , err )
2022-05-18 16:33:33 +00:00
require . Equal ( t , workspace . LatestBuild . BuildNumber + 1 , build . BuildNumber )
2022-03-22 19:17:50 +00:00
coderdtest . AwaitWorkspaceBuildJob ( t , client , build . ID )
2022-11-10 18:25:46 +00:00
res , err := client . Workspaces ( ctx , codersdk . WorkspaceFilter {
2022-06-03 19:36:08 +00:00
Owner : user . UserID . String ( ) ,
} )
2022-03-07 17:40:54 +00:00
require . NoError ( t , err )
2022-11-10 18:25:46 +00:00
require . Len ( t , res . Workspaces , 0 )
2022-03-07 17:40:54 +00:00
} )
2022-02-06 00:24:51 +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 )
_ = coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
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
_ = coderdtest . AwaitWorkspaceBuildJob ( t , client , workspace . LatestBuild . ID )
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" )
sched , err := schedule . 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 ,
MaxTTL : 0 ,
} , 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 )
_ = coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
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
_ = coderdtest . AwaitWorkspaceBuildJob ( t , client , workspace . LatestBuild . ID )
// 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" ,
ttlMillis : ptr . Ref ( ( 24 * 7 * time . Hour ) . Milliseconds ( ) ) ,
expectedError : "" ,
2022-04-07 09:03:35 +00:00
} ,
2022-05-30 19:19:17 +00:00
{
name : "above maximum ttl" ,
2022-06-02 10:23:34 +00:00
ttlMillis : ptr . Ref ( ( 24 * 7 * time . Hour + time . Minute ) . Milliseconds ( ) ) ,
2022-07-27 21:20:02 +00:00
expectedError : "time until shutdown must be less than 7 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 )
_ = coderdtest . AwaitTemplateVersionJob ( 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
} )
2022-06-09 21:10:24 +00:00
_ = coderdtest . AwaitWorkspaceBuildJob ( 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 ,
MaxTTL : 0 ,
} , 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 )
_ = coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
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
_ = coderdtest . AwaitWorkspaceBuildJob ( t , client , workspace . LatestBuild . ID )
// 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 )
_ = coderdtest . AwaitTemplateVersionJob ( 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 ( ) )
} )
_ = coderdtest . AwaitWorkspaceBuildJob ( 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
}
2022-05-18 21:16:26 +00:00
func TestWorkspaceWatcher ( t * testing . T ) {
t . Parallel ( )
2022-11-07 15:25:18 +00:00
client , closeFunc := coderdtest . NewWithProvisionerCloser ( t , & coderdtest . Options { IncludeProvisionerDaemon : true } )
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 ,
ProvisionPlan : echo . ProvisionComplete ,
ProvisionApply : [ ] * proto . Provision_Response { {
2022-11-07 15:25:18 +00:00
Type : & proto . Provision_Response_Complete {
Complete : & proto . Provision_Complete {
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
} } ,
} } ,
} ,
} ,
} } ,
} )
2022-05-18 21:16:26 +00:00
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
2022-11-07 15:25:18 +00:00
coderdtest . AwaitWorkspaceBuildJob ( 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 ) {
logger . Info ( ctx , "done waiting for event" , slog . F ( "event" , event ) )
return
}
}
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-01-29 21:47:24 +00:00
agentClient := agentsdk . New ( client . URL )
2022-11-09 13:31:24 +00:00
agentClient . SetSessionToken ( authToken )
2022-11-07 15:25:18 +00:00
agentCloser := agent . New ( agent . Options {
Client : agentClient ,
2022-11-22 11:01:28 +00:00
Logger : logger . Named ( "agent" ) ,
2022-11-07 15:25:18 +00:00
} )
defer func ( ) {
_ = agentCloser . Close ( )
} ( )
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
} )
2022-11-07 15:25:18 +00:00
agentCloser . 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
closeFunc . Close ( )
build := coderdtest . CreateWorkspaceBuild ( t , client , workspace , database . WorkspaceTransitionStart )
2023-02-13 09:54:43 +00:00
wait ( "first is for the workspace build itself" , nil )
2022-11-07 15:25:18 +00:00
err = client . CancelWorkspaceBuild ( ctx , build . ID )
2022-05-18 21:16:26 +00:00
require . NoError ( t , err )
2023-02-13 09:54:43 +00:00
wait ( "second is for the build cancel" , nil )
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
2022-11-09 17:01:34 +00:00
err = client . UpdateActiveTemplateVersion ( ctx , template . ID , codersdk . UpdateActiveTemplateVersion {
ID : template . ActiveVersionID ,
} )
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
2022-05-18 21:16:26 +00:00
cancel ( )
}
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 ,
2022-11-11 22:45:58 +00:00
ProvisionApply : [ ] * proto . Provision_Response { {
2022-10-03 21:01:13 +00:00
Type : & proto . Provision_Response_Complete {
Complete : & proto . Provision_Complete {
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" ,
} } ,
} ,
} ,
} } ,
} )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
coderdtest . AwaitWorkspaceBuildJob ( 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 , 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 ,
2022-11-11 22:45:58 +00:00
ProvisionApply : [ ] * proto . Provision_Response { {
2022-10-03 21:01:13 +00:00
Type : & proto . Provision_Response_Complete {
Complete : & proto . Provision_Complete {
Resources : [ ] * proto . Resource { {
Name : "some" ,
Type : "example" ,
Agents : [ ] * proto . Agent { {
Id : "something" ,
Auth : & proto . Agent_Token { } ,
Apps : apps ,
} } ,
} } ,
} ,
} ,
} } ,
} )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
coderdtest . AwaitWorkspaceBuildJob ( 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 , 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 )
} )
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 ,
2022-11-11 22:45:58 +00:00
ProvisionApply : [ ] * proto . Provision_Response { {
2022-10-03 21:01:13 +00:00
Type : & proto . Provision_Response_Complete {
Complete : & proto . Provision_Complete {
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 ,
} } ,
} } ,
} ,
} ,
} } ,
} )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
template := coderdtest . CreateTemplate ( t , client , user . OrganizationID , version . ID )
workspace := coderdtest . CreateWorkspace ( t , client , user . OrganizationID , template . ID )
coderdtest . AwaitWorkspaceBuildJob ( 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 )
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 ,
ProvisionPlan : [ ] * proto . Provision_Response {
{
Type : & proto . Provision_Response_Complete {
Complete : & proto . Provision_Complete {
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 ,
ValidationMin : 1 ,
ValidationMax : 3 ,
ValidationMonotonic : string ( secondParameterValidationMonotonic ) ,
} ,
2023-01-17 15:24:45 +00:00
} ,
} ,
} ,
2023-02-19 00:32:09 +00:00
} ,
} ,
2023-01-17 15:24:45 +00:00
ProvisionApply : [ ] * proto . Provision_Response { {
Type : & proto . Provision_Response_Complete {
Complete : & proto . Provision_Complete { } ,
} ,
} } ,
} )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
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
} )
workspaceBuild := coderdtest . AwaitWorkspaceBuildJob ( t , client , workspace . LatestBuild . ID )
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 ,
ProvisionPlan : [ ] * proto . Provision_Response {
{
Type : & proto . Provision_Response_Complete {
Complete : & proto . Provision_Complete {
Parameters : [ ] * proto . RichParameter {
{
Name : firstParameterName ,
Type : firstParameterType ,
Description : firstParameterDescription ,
DefaultValue : firstParameterDefaultValue ,
} ,
{
Name : secondParameterName ,
Type : secondParameterType ,
Description : secondParameterDescription ,
Required : secondParameterRequired ,
} ,
} ,
} ,
} ,
} ,
} ,
ProvisionApply : [ ] * proto . Provision_Response { {
Type : & proto . Provision_Response_Complete {
Complete : & proto . Provision_Complete { } ,
} ,
} } ,
} )
coderdtest . AwaitTemplateVersionJob ( t , client , version . ID )
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 )
expectedBuildParameters := [ ] codersdk . WorkspaceBuildParameter {
// First parameter is optional, so coder will pick the default value.
{ 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
} )
workspaceBuild := coderdtest . AwaitWorkspaceBuildJob ( t , client , workspace . LatestBuild . ID )
require . Equal ( t , codersdk . WorkspaceStatusRunning , workspaceBuild . Status )
workspaceBuildParameters , err := client . WorkspaceBuildParameters ( ctx , workspaceBuild . ID )
require . NoError ( t , err )
require . ElementsMatch ( t , expectedBuildParameters , workspaceBuildParameters )
}