@@ -2842,9 +2842,12 @@ func TestCompleteJob(t *testing.T) {
28422842// has_ai_task has a default value of nil, but once the workspace build completes it will have a value;
28432843// it is set to "true" if the related template has any coder_ai_task resources defined, and its sidebar app ID
28442844// 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.
28452847t .Run ("WorkspaceBuild" ,func (t * testing.T ) {
28462848type testcase struct {
28472849name string
2850+ seedFunc func (context.Context , testing.TB , database.Store )error // If you need to insert other resources
28482851transition database.WorkspaceTransition
28492852input * proto.CompletedJob_WorkspaceBuild
28502853expectHasAiTask bool
@@ -2944,6 +2947,17 @@ func TestCompleteJob(t *testing.T) {
29442947expectHasAiTask :true ,
29452948expectUsageEvent :false ,
29462949},
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+ },
29472961} {
29482962t .Run (tc .name ,func (t * testing.T ) {
29492963t .Parallel ()
@@ -2980,6 +2994,9 @@ func TestCompleteJob(t *testing.T) {
29802994})
29812995
29822996ctx := testutil .Context (t ,testutil .WaitShort )
2997+ if tc .seedFunc != nil {
2998+ require .NoError (t ,tc .seedFunc (ctx ,t ,db ))
2999+ }
29833000
29843001buildJobID := uuid .New ()
29853002wsBuildID := uuid .New ()
@@ -2999,8 +3016,13 @@ func TestCompleteJob(t *testing.T) {
29993016Tags :pd .Tags ,
30003017})
30013018require .NoError (t ,err )
3019+ var buildNum int32
3020+ if latestBuild ,err := db .GetLatestWorkspaceBuildByWorkspaceID (ctx ,workspaceTable .ID );err == nil {
3021+ buildNum = latestBuild .BuildNumber
3022+ }
30023023build := dbgen .WorkspaceBuild (t ,db , database.WorkspaceBuild {
30033024ID :wsBuildID ,
3025+ BuildNumber :buildNum + 1 ,
30043026JobID :buildJobID ,
30053027WorkspaceID :workspaceTable .ID ,
30063028TemplateVersionID :version .ID ,
@@ -3038,7 +3060,7 @@ func TestCompleteJob(t *testing.T) {
30383060require .True (t ,build .HasAITask .Valid )// We ALWAYS expect a value to be set, therefore not nil, i.e. valid = true.
30393061require .Equal (t ,tc .expectHasAiTask ,build .HasAITask .Bool )
30403062
3041- if tc .expectHasAiTask {
3063+ if tc .expectHasAiTask && build . Transition != database . WorkspaceTransitionStop {
30423064require .Equal (t ,sidebarAppID ,build .AITaskSidebarAppID .UUID .String ())
30433065}
30443066
@@ -4244,3 +4266,63 @@ func (f *fakeUsageInserter) InsertDiscreteUsageEvent(_ context.Context, _ databa
42444266f .collectedEvents = append (f .collectedEvents ,event )
42454267return nil
42464268}
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+ }