@@ -1564,6 +1564,69 @@ func TestCompleteJob(t *testing.T) {
15641564require .Contains (t ,job .Error .String ,`external auth provider "github" is not configured` )
15651565})
15661566
1567+ t .Run ("TemplateImport_WithAITasks" ,func (t * testing.T ) {
1568+ t .Parallel ()
1569+
1570+ t .Run ("WithoutPromptParam" ,func (t * testing.T ) {
1571+ t .Parallel ()
1572+
1573+ srv ,db ,_ ,pd := setup (t ,false ,& overrides {})
1574+ jobID := uuid .New ()
1575+ versionID := uuid .New ()
1576+ err := db .InsertTemplateVersion (ctx , database.InsertTemplateVersionParams {
1577+ ID :versionID ,
1578+ JobID :jobID ,
1579+ OrganizationID :pd .OrganizationID ,
1580+ })
1581+ require .NoError (t ,err )
1582+ job ,err := db .InsertProvisionerJob (ctx , database.InsertProvisionerJobParams {
1583+ ID :jobID ,
1584+ Provisioner :database .ProvisionerTypeEcho ,
1585+ Input : []byte (`{"template_version_id": "` + versionID .String ()+ `"}` ),
1586+ StorageMethod :database .ProvisionerStorageMethodFile ,
1587+ Type :database .ProvisionerJobTypeWorkspaceBuild ,
1588+ OrganizationID :pd .OrganizationID ,
1589+ Tags :pd .Tags ,
1590+ })
1591+ require .NoError (t ,err )
1592+ _ ,err = db .AcquireProvisionerJob (ctx , database.AcquireProvisionerJobParams {
1593+ OrganizationID :pd .OrganizationID ,
1594+ WorkerID : uuid.NullUUID {
1595+ UUID :pd .ID ,
1596+ Valid :true ,
1597+ },
1598+ Types : []database.ProvisionerType {database .ProvisionerTypeEcho },
1599+ StartedAt : sql.NullTime {
1600+ Time :dbtime .Now (),
1601+ Valid :true ,
1602+ },
1603+ ProvisionerTags :must (json .Marshal (job .Tags )),
1604+ })
1605+ require .NoError (t ,err )
1606+ completeJob := func () {
1607+ _ ,err = srv .CompleteJob (ctx ,& proto.CompletedJob {
1608+ JobId :job .ID .String (),
1609+ Type :& proto.CompletedJob_TemplateImport_ {
1610+ TemplateImport :& proto.CompletedJob_TemplateImport {
1611+ StartResources : []* sdkproto.Resource {{
1612+ Name :"my_task" ,
1613+ Type :"coder_ai_task" ,
1614+ }},
1615+ StopResources : []* sdkproto.Resource {},
1616+ HasAiTasks :true ,
1617+ Plan : []byte ("{}" ),
1618+ },
1619+ },
1620+ })
1621+ require .NoError (t ,err )
1622+ }
1623+ completeJob ()
1624+ job ,err = db .GetProvisionerJobByID (ctx ,job .ID )
1625+ require .NoError (t ,err )
1626+ require .Contains (t ,job .Error .String ,`external auth provider "github" is not configured` )
1627+ })
1628+ })
1629+
15671630t .Run ("TemplateImport_WithGitAuth" ,func (t * testing.T ) {
15681631t .Parallel ()
15691632srv ,db ,_ ,pd := setup (t ,false ,& overrides {
@@ -2435,6 +2498,233 @@ func TestCompleteJob(t *testing.T) {
24352498testutil .RequireReceive (ctx ,t ,done )
24362499require .Equal (t ,replacements ,orchestrator .replacements )
24372500})
2501+
2502+ t .Run ("AITasks" ,func (t * testing.T ) {
2503+ t .Parallel ()
2504+
2505+ // has_ai_task has a default value of nil, but once the template import completes it will have a value;
2506+ // it is set to "true" if the template has any coder_ai_task resources defined.
2507+ t .Run ("TemplateImport" ,func (t * testing.T ) {
2508+ type testcase struct {
2509+ name string
2510+ input * proto.CompletedJob_TemplateImport
2511+ expected bool
2512+ }
2513+
2514+ for _ ,tc := range []testcase {
2515+ {
2516+ name :"has_ai_task is false by default" ,
2517+ input :& proto.CompletedJob_TemplateImport {
2518+ // HasAiTasks is not set.
2519+ Plan : []byte ("{}" ),
2520+ },
2521+ expected :false ,
2522+ },
2523+ {
2524+ name :"has_ai_task gets set to true" ,
2525+ input :& proto.CompletedJob_TemplateImport {
2526+ HasAiTasks :true ,
2527+ Plan : []byte ("{}" ),
2528+ },
2529+ expected :true ,
2530+ },
2531+ } {
2532+ t .Run (tc .name ,func (t * testing.T ) {
2533+ t .Parallel ()
2534+
2535+ srv ,db ,_ ,pd := setup (t ,false ,& overrides {})
2536+
2537+ importJobID := uuid .New ()
2538+ tvID := uuid .New ()
2539+ template := dbgen .Template (t ,db , database.Template {
2540+ Name :"template" ,
2541+ Provisioner :database .ProvisionerTypeEcho ,
2542+ OrganizationID :pd .OrganizationID ,
2543+ })
2544+ version := dbgen .TemplateVersion (t ,db , database.TemplateVersion {
2545+ ID :tvID ,
2546+ OrganizationID :pd .OrganizationID ,
2547+ TemplateID : uuid.NullUUID {
2548+ UUID :template .ID ,
2549+ Valid :true ,
2550+ },
2551+ JobID :importJobID ,
2552+ })
2553+ _ = version
2554+
2555+ ctx := testutil .Context (t ,testutil .WaitShort )
2556+ job ,err := db .InsertProvisionerJob (ctx , database.InsertProvisionerJobParams {
2557+ ID :importJobID ,
2558+ CreatedAt :dbtime .Now (),
2559+ UpdatedAt :dbtime .Now (),
2560+ OrganizationID :pd .OrganizationID ,
2561+ InitiatorID :uuid .New (),
2562+ Input :must (json .Marshal (provisionerdserver.TemplateVersionImportJob {
2563+ TemplateVersionID :tvID ,
2564+ })),
2565+ Provisioner :database .ProvisionerTypeEcho ,
2566+ StorageMethod :database .ProvisionerStorageMethodFile ,
2567+ Type :database .ProvisionerJobTypeTemplateVersionImport ,
2568+ Tags :pd .Tags ,
2569+ })
2570+ require .NoError (t ,err )
2571+
2572+ _ ,err = db .AcquireProvisionerJob (ctx , database.AcquireProvisionerJobParams {
2573+ OrganizationID :pd .OrganizationID ,
2574+ WorkerID : uuid.NullUUID {
2575+ UUID :pd .ID ,
2576+ Valid :true ,
2577+ },
2578+ Types : []database.ProvisionerType {database .ProvisionerTypeEcho },
2579+ ProvisionerTags :must (json .Marshal (job .Tags )),
2580+ StartedAt : sql.NullTime {Time :job .CreatedAt ,Valid :true },
2581+ })
2582+ require .NoError (t ,err )
2583+
2584+ version ,err = db .GetTemplateVersionByID (ctx ,tvID )
2585+ require .NoError (t ,err )
2586+ require .False (t ,version .HasAITask .Valid )// Value should be nil (i.e. valid = false).
2587+
2588+ completedJob := proto.CompletedJob {
2589+ JobId :job .ID .String (),
2590+ Type :& proto.CompletedJob_TemplateImport_ {
2591+ TemplateImport :tc .input ,
2592+ },
2593+ }
2594+ _ ,err = srv .CompleteJob (ctx ,& completedJob )
2595+ require .NoError (t ,err )
2596+
2597+ version ,err = db .GetTemplateVersionByID (ctx ,tvID )
2598+ require .NoError (t ,err )
2599+ require .True (t ,version .HasAITask .Valid )// We ALWAYS expect a value to be set, therefore not nil, i.e. valid = true.
2600+ require .Equal (t ,tc .expected ,version .HasAITask .Bool )
2601+ })
2602+ }
2603+ })
2604+
2605+ // has_ai_task has a default value of nil, but once the workspace build completes it will have a value;
2606+ // it is set to "true" if the related template has any coder_ai_task resources defined, and its sidebar app ID
2607+ // will be set as well in that case.
2608+ t .Run ("WorkspaceBuild" ,func (t * testing.T ) {
2609+ type testcase struct {
2610+ name string
2611+ input * proto.CompletedJob_WorkspaceBuild
2612+ expected bool
2613+ }
2614+
2615+ sidebarAppID := uuid .NewString ()
2616+ for _ ,tc := range []testcase {
2617+ {
2618+ name :"has_ai_task is false by default" ,
2619+ input :& proto.CompletedJob_WorkspaceBuild {
2620+ // No AiTasks defined.
2621+ },
2622+ expected :false ,
2623+ },
2624+ {
2625+ name :"has_ai_task is set to true" ,
2626+ input :& proto.CompletedJob_WorkspaceBuild {
2627+ AiTasks : []* sdkproto.AITask {
2628+ {
2629+ Id :uuid .NewString (),
2630+ SidebarApp :& sdkproto.AITaskSidebarApp {
2631+ Id :sidebarAppID ,
2632+ },
2633+ },
2634+ },
2635+ },
2636+ expected :true ,
2637+ },
2638+ } {
2639+ t .Run (tc .name ,func (t * testing.T ) {
2640+ t .Parallel ()
2641+
2642+ srv ,db ,_ ,pd := setup (t ,false ,& overrides {})
2643+
2644+ importJobID := uuid .New ()
2645+ tvID := uuid .New ()
2646+ template := dbgen .Template (t ,db , database.Template {
2647+ Name :"template" ,
2648+ Provisioner :database .ProvisionerTypeEcho ,
2649+ OrganizationID :pd .OrganizationID ,
2650+ })
2651+ version := dbgen .TemplateVersion (t ,db , database.TemplateVersion {
2652+ ID :tvID ,
2653+ OrganizationID :pd .OrganizationID ,
2654+ TemplateID : uuid.NullUUID {
2655+ UUID :template .ID ,
2656+ Valid :true ,
2657+ },
2658+ JobID :importJobID ,
2659+ })
2660+ user := dbgen .User (t ,db , database.User {})
2661+ workspaceTable := dbgen .Workspace (t ,db , database.WorkspaceTable {
2662+ TemplateID :template .ID ,
2663+ OwnerID :user .ID ,
2664+ OrganizationID :pd .OrganizationID ,
2665+ })
2666+ build := dbgen .WorkspaceBuild (t ,db , database.WorkspaceBuild {
2667+ WorkspaceID :workspaceTable .ID ,
2668+ TemplateVersionID :version .ID ,
2669+ InitiatorID :user .ID ,
2670+ Transition :database .WorkspaceTransitionStart ,
2671+ })
2672+
2673+ ctx := testutil .Context (t ,testutil .WaitShort )
2674+ job ,err := db .InsertProvisionerJob (ctx , database.InsertProvisionerJobParams {
2675+ ID :importJobID ,
2676+ CreatedAt :dbtime .Now (),
2677+ UpdatedAt :dbtime .Now (),
2678+ OrganizationID :pd .OrganizationID ,
2679+ InitiatorID :uuid .New (),
2680+ Input :must (json .Marshal (provisionerdserver.WorkspaceProvisionJob {
2681+ WorkspaceBuildID :build .ID ,
2682+ LogLevel :"DEBUG" ,
2683+ })),
2684+ Provisioner :database .ProvisionerTypeEcho ,
2685+ StorageMethod :database .ProvisionerStorageMethodFile ,
2686+ Type :database .ProvisionerJobTypeWorkspaceBuild ,
2687+ Tags :pd .Tags ,
2688+ })
2689+ require .NoError (t ,err )
2690+
2691+ _ ,err = db .AcquireProvisionerJob (ctx , database.AcquireProvisionerJobParams {
2692+ OrganizationID :pd .OrganizationID ,
2693+ WorkerID : uuid.NullUUID {
2694+ UUID :pd .ID ,
2695+ Valid :true ,
2696+ },
2697+ Types : []database.ProvisionerType {database .ProvisionerTypeEcho },
2698+ ProvisionerTags :must (json .Marshal (job .Tags )),
2699+ StartedAt : sql.NullTime {Time :job .CreatedAt ,Valid :true },
2700+ })
2701+ require .NoError (t ,err )
2702+
2703+ build ,err = db .GetWorkspaceBuildByID (ctx ,build .ID )
2704+ require .NoError (t ,err )
2705+ require .False (t ,build .HasAITask .Valid )// Value should be nil (i.e. valid = false).
2706+
2707+ completedJob := proto.CompletedJob {
2708+ JobId :job .ID .String (),
2709+ Type :& proto.CompletedJob_WorkspaceBuild_ {
2710+ WorkspaceBuild :tc .input ,
2711+ },
2712+ }
2713+ _ ,err = srv .CompleteJob (ctx ,& completedJob )
2714+ require .NoError (t ,err )
2715+
2716+ build ,err = db .GetWorkspaceBuildByID (ctx ,build .ID )
2717+ require .NoError (t ,err )
2718+ require .True (t ,build .HasAITask .Valid )// We ALWAYS expect a value to be set, therefore not nil, i.e. valid = true.
2719+ require .Equal (t ,tc .expected ,build .HasAITask .Bool )
2720+
2721+ if tc .expected {
2722+ require .Equal (t ,sidebarAppID ,build .AITaskSidebarAppID .UUID .String ())
2723+ }
2724+ })
2725+ }
2726+ })
2727+ })
24382728}
24392729
24402730type mockPrebuildsOrchestrator struct {