@@ -2842,9 +2842,12 @@ func TestCompleteJob(t *testing.T) {
2842
2842
// has_ai_task has a default value of nil, but once the workspace build completes it will have a value;
2843
2843
// it is set to "true" if the related template has any coder_ai_task resources defined, and its sidebar app ID
2844
2844
// will be set as well in that case.
2845
+ // HACK(johnstcn): we also set it to "true" if any _previous_ workspace builds ever had it set to "true".
2846
+ // This is to avoid tasks "disappearing" when you stop them.
2845
2847
t .Run ("WorkspaceBuild" ,func (t * testing.T ) {
2846
2848
type testcase struct {
2847
2849
name string
2850
+ seedFunc func (context.Context , testing.TB , database.Store )error // If you need to insert other resources
2848
2851
transition database.WorkspaceTransition
2849
2852
input * proto.CompletedJob_WorkspaceBuild
2850
2853
expectHasAiTask bool
@@ -2944,6 +2947,17 @@ func TestCompleteJob(t *testing.T) {
2944
2947
expectHasAiTask :true ,
2945
2948
expectUsageEvent :false ,
2946
2949
},
2950
+ {
2951
+ name :"current build does not have ai task but previous build did" ,
2952
+ seedFunc :seedPreviousWorkspaceStartWithAITask ,
2953
+ transition :database .WorkspaceTransitionStop ,
2954
+ input :& proto.CompletedJob_WorkspaceBuild {
2955
+ AiTasks : []* sdkproto.AITask {},
2956
+ Resources : []* sdkproto.Resource {},
2957
+ },
2958
+ expectHasAiTask :true ,
2959
+ expectUsageEvent :false ,
2960
+ },
2947
2961
} {
2948
2962
t .Run (tc .name ,func (t * testing.T ) {
2949
2963
t .Parallel ()
@@ -2980,6 +2994,9 @@ func TestCompleteJob(t *testing.T) {
2980
2994
})
2981
2995
2982
2996
ctx := testutil .Context (t ,testutil .WaitShort )
2997
+ if tc .seedFunc != nil {
2998
+ require .NoError (t ,tc .seedFunc (ctx ,t ,db ))
2999
+ }
2983
3000
2984
3001
buildJobID := uuid .New ()
2985
3002
wsBuildID := uuid .New ()
@@ -2999,8 +3016,13 @@ func TestCompleteJob(t *testing.T) {
2999
3016
Tags :pd .Tags ,
3000
3017
})
3001
3018
require .NoError (t ,err )
3019
+ var buildNum int32
3020
+ if latestBuild ,err := db .GetLatestWorkspaceBuildByWorkspaceID (ctx ,workspaceTable .ID );err == nil {
3021
+ buildNum = latestBuild .BuildNumber
3022
+ }
3002
3023
build := dbgen .WorkspaceBuild (t ,db , database.WorkspaceBuild {
3003
3024
ID :wsBuildID ,
3025
+ BuildNumber :buildNum + 1 ,
3004
3026
JobID :buildJobID ,
3005
3027
WorkspaceID :workspaceTable .ID ,
3006
3028
TemplateVersionID :version .ID ,
@@ -3038,7 +3060,7 @@ func TestCompleteJob(t *testing.T) {
3038
3060
require .True (t ,build .HasAITask .Valid )// We ALWAYS expect a value to be set, therefore not nil, i.e. valid = true.
3039
3061
require .Equal (t ,tc .expectHasAiTask ,build .HasAITask .Bool )
3040
3062
3041
- if tc .expectHasAiTask {
3063
+ if tc .expectHasAiTask && build . Transition != database . WorkspaceTransitionStop {
3042
3064
require .Equal (t ,sidebarAppID ,build .AITaskSidebarAppID .UUID .String ())
3043
3065
}
3044
3066
@@ -4244,3 +4266,63 @@ func (f *fakeUsageInserter) InsertDiscreteUsageEvent(_ context.Context, _ databa
4244
4266
f .collectedEvents = append (f .collectedEvents ,event )
4245
4267
return nil
4246
4268
}
4269
+
4270
+ func seedPreviousWorkspaceStartWithAITask (ctx context.Context ,t testing.TB ,db database.Store )error {
4271
+ t .Helper ()
4272
+ // If the below looks slightly convoluted, that's because it is.
4273
+ // The workspace doesn't yet have a latest build, so querying all
4274
+ // workspaces will fail.
4275
+ tpls ,err := db .GetTemplates (ctx )
4276
+ if err != nil {
4277
+ return xerrors .Errorf ("seedFunc: get template: %w" ,err )
4278
+ }
4279
+ if len (tpls )!= 1 {
4280
+ return xerrors .Errorf ("seedFunc: expected exactly one template, got %d" ,len (tpls ))
4281
+ }
4282
+ ws ,err := db .GetWorkspacesByTemplateID (ctx ,tpls [0 ].ID )
4283
+ if err != nil {
4284
+ return xerrors .Errorf ("seedFunc: get workspaces: %w" ,err )
4285
+ }
4286
+ if len (ws )!= 1 {
4287
+ return xerrors .Errorf ("seedFunc: expected exactly one workspace, got %d" ,len (ws ))
4288
+ }
4289
+ w := ws [0 ]
4290
+ prevJob := dbgen .ProvisionerJob (t ,db ,nil , database.ProvisionerJob {
4291
+ OrganizationID :w .OrganizationID ,
4292
+ InitiatorID :w .OwnerID ,
4293
+ Type :database .ProvisionerJobTypeWorkspaceBuild ,
4294
+ })
4295
+ tvs ,err := db .GetTemplateVersionsByTemplateID (ctx , database.GetTemplateVersionsByTemplateIDParams {
4296
+ TemplateID :tpls [0 ].ID ,
4297
+ })
4298
+ if err != nil {
4299
+ return xerrors .Errorf ("seedFunc: get template version: %w" ,err )
4300
+ }
4301
+ if len (tvs )!= 1 {
4302
+ return xerrors .Errorf ("seedFunc: expected exactly one template version, got %d" ,len (tvs ))
4303
+ }
4304
+ if tpls [0 ].ActiveVersionID == uuid .Nil {
4305
+ return xerrors .Errorf ("seedFunc: active version id is nil" )
4306
+ }
4307
+ res := dbgen .WorkspaceResource (t ,db , database.WorkspaceResource {
4308
+ JobID :prevJob .ID ,
4309
+ })
4310
+ agt := dbgen .WorkspaceAgent (t ,db , database.WorkspaceAgent {
4311
+ ResourceID :res .ID ,
4312
+ })
4313
+ wa := dbgen .WorkspaceApp (t ,db , database.WorkspaceApp {
4314
+ AgentID :agt .ID ,
4315
+ })
4316
+ _ = dbgen .WorkspaceBuild (t ,db , database.WorkspaceBuild {
4317
+ BuildNumber :1 ,
4318
+ HasAITask : sql.NullBool {Valid :true ,Bool :true },
4319
+ AITaskSidebarAppID : uuid.NullUUID {Valid :true ,UUID :wa .ID },
4320
+ ID :w .ID ,
4321
+ InitiatorID :w .OwnerID ,
4322
+ JobID :prevJob .ID ,
4323
+ TemplateVersionID :tvs [0 ].ID ,
4324
+ Transition :database .WorkspaceTransitionStart ,
4325
+ WorkspaceID :w .ID ,
4326
+ })
4327
+ return nil
4328
+ }