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