@@ -1764,3 +1764,177 @@ func TestExecutorAutostartSkipsWhenNoProvisionersAvailable(t *testing.T) {
17641764
17651765 assert .Len (t , stats .Transitions , 1 , "should create builds when provisioners are available" )
17661766}
1767+
1768+ func TestExecutorTaskWorkspace (t * testing.T ) {
1769+ t .Parallel ()
1770+
1771+ createTaskTemplate := func (t * testing.T , client * codersdk.Client , orgID uuid.UUID , ctx context.Context , defaultTTL time.Duration ) codersdk.Template {
1772+ t .Helper ()
1773+
1774+ taskAppID := uuid .New ()
1775+ version := coderdtest .CreateTemplateVersion (t , client , orgID , & echo.Responses {
1776+ Parse : echo .ParseComplete ,
1777+ ProvisionPlan : []* proto.Response {
1778+ {
1779+ Type : & proto.Response_Plan {
1780+ Plan : & proto.PlanComplete {HasAiTasks : true },
1781+ },
1782+ },
1783+ },
1784+ ProvisionApply : []* proto.Response {
1785+ {
1786+ Type : & proto.Response_Apply {
1787+ Apply : & proto.ApplyComplete {
1788+ Resources : []* proto.Resource {
1789+ {
1790+ Agents : []* proto.Agent {
1791+ {
1792+ Id : uuid .NewString (),
1793+ Name : "dev" ,
1794+ Auth : & proto.Agent_Token {
1795+ Token : uuid .NewString (),
1796+ },
1797+ Apps : []* proto.App {
1798+ {
1799+ Id : taskAppID .String (),
1800+ Slug : "task-app" ,
1801+ },
1802+ },
1803+ },
1804+ },
1805+ },
1806+ },
1807+ AiTasks : []* proto.AITask {
1808+ {
1809+ AppId : taskAppID .String (),
1810+ },
1811+ },
1812+ },
1813+ },
1814+ },
1815+ },
1816+ })
1817+ coderdtest .AwaitTemplateVersionJobCompleted (t , client , version .ID )
1818+ template := coderdtest .CreateTemplate (t , client , orgID , version .ID )
1819+
1820+ if defaultTTL > 0 {
1821+ _ , err := client .UpdateTemplateMeta (ctx , template .ID , codersdk.UpdateTemplateMeta {
1822+ DefaultTTLMillis : defaultTTL .Milliseconds (),
1823+ })
1824+ require .NoError (t , err )
1825+ }
1826+
1827+ return template
1828+ }
1829+
1830+ createTaskWorkspace := func (t * testing.T , client * codersdk.Client , template codersdk.Template , ctx context.Context , input string ) codersdk.Workspace {
1831+ t .Helper ()
1832+
1833+ exp := codersdk .NewExperimentalClient (client )
1834+ task , err := exp .CreateTask (ctx , "me" , codersdk.CreateTaskRequest {
1835+ TemplateVersionID : template .ActiveVersionID ,
1836+ Input : input ,
1837+ })
1838+ require .NoError (t , err )
1839+ require .True (t , task .WorkspaceID .Valid , "task should have a workspace" )
1840+
1841+ workspace , err := client .Workspace (ctx , task .WorkspaceID .UUID )
1842+ require .NoError (t , err )
1843+ coderdtest .AwaitWorkspaceBuildJobCompleted (t , client , workspace .LatestBuild .ID )
1844+
1845+ return workspace
1846+ }
1847+
1848+ t .Run ("Autostart" , func (t * testing.T ) {
1849+ t .Parallel ()
1850+
1851+ var (
1852+ sched = mustSchedule (t , "CRON_TZ=UTC 0 * * * *" )
1853+ tickCh = make (chan time.Time )
1854+ statsCh = make (chan autobuild.Stats )
1855+ client , db = coderdtest .NewWithDatabase (t , & coderdtest.Options {
1856+ AutobuildTicker : tickCh ,
1857+ IncludeProvisionerDaemon : true ,
1858+ AutobuildStats : statsCh ,
1859+ })
1860+ )
1861+
1862+ admin := coderdtest .CreateFirstUser (t , client )
1863+ ctx := testutil .Context (t , testutil .WaitLong )
1864+
1865+ // Given: A task workspace
1866+ template := createTaskTemplate (t , client , admin .OrganizationID , ctx , 0 )
1867+ workspace := createTaskWorkspace (t , client , template , ctx , "test task for autostart" )
1868+
1869+ // Given: The task workspace has an autostart schedule
1870+ err := client .UpdateWorkspaceAutostart (ctx , workspace .ID , codersdk.UpdateWorkspaceAutostartRequest {
1871+ Schedule : ptr .Ref (sched .String ()),
1872+ })
1873+ require .NoError (t , err )
1874+
1875+ // Given: That the workspace is in a stopped state.
1876+ workspace = coderdtest .MustTransitionWorkspace (t , client , workspace .ID , codersdk .WorkspaceTransitionStart , codersdk .WorkspaceTransitionStop )
1877+
1878+ p , err := coderdtest .GetProvisionerForTags (db , time .Now (), workspace .OrganizationID , map [string ]string {})
1879+ require .NoError (t , err )
1880+
1881+ // When: the autobuild executor ticks after the scheduled time
1882+ go func () {
1883+ tickTime := sched .Next (workspace .LatestBuild .CreatedAt )
1884+ coderdtest .UpdateProvisionerLastSeenAt (t , db , p .ID , tickTime )
1885+ tickCh <- tickTime
1886+ close (tickCh )
1887+ }()
1888+
1889+ // Then: We expect to see a start transition
1890+ stats := <- statsCh
1891+ require .Len (t , stats .Transitions , 1 , "lifecycle executor should transition the task workspace" )
1892+ assert .Contains (t , stats .Transitions , workspace .ID , "task workspace should be in transitions" )
1893+ assert .Equal (t , database .WorkspaceTransitionStart , stats .Transitions [workspace .ID ], "should autostart the workspace" )
1894+ require .Empty (t , stats .Errors , "should have no errors when managing task workspaces" )
1895+ })
1896+
1897+ t .Run ("Autostop" , func (t * testing.T ) {
1898+ t .Parallel ()
1899+
1900+ var (
1901+ tickCh = make (chan time.Time )
1902+ statsCh = make (chan autobuild.Stats )
1903+ client , db = coderdtest .NewWithDatabase (t , & coderdtest.Options {
1904+ AutobuildTicker : tickCh ,
1905+ IncludeProvisionerDaemon : true ,
1906+ AutobuildStats : statsCh ,
1907+ })
1908+ )
1909+
1910+ admin := coderdtest .CreateFirstUser (t , client )
1911+ ctx := testutil .Context (t , testutil .WaitLong )
1912+
1913+ // Given: A task workspace with an 8 hour deadline
1914+ template := createTaskTemplate (t , client , admin .OrganizationID , ctx , 8 * time .Hour )
1915+ workspace := createTaskWorkspace (t , client , template , ctx , "test task for autostop" )
1916+
1917+ // Given: The workspace is currently running
1918+ workspace = coderdtest .MustWorkspace (t , client , workspace .ID )
1919+ require .Equal (t , codersdk .WorkspaceTransitionStart , workspace .LatestBuild .Transition )
1920+ require .NotZero (t , workspace .LatestBuild .Deadline , "workspace should have a deadline for autostop" )
1921+
1922+ p , err := coderdtest .GetProvisionerForTags (db , time .Now (), workspace .OrganizationID , map [string ]string {})
1923+ require .NoError (t , err )
1924+
1925+ // When: the autobuild executor ticks after the deadline
1926+ go func () {
1927+ tickTime := workspace .LatestBuild .Deadline .Time .Add (time .Minute )
1928+ coderdtest .UpdateProvisionerLastSeenAt (t , db , p .ID , tickTime )
1929+ tickCh <- tickTime
1930+ close (tickCh )
1931+ }()
1932+
1933+ // Then: We expect to see a stop transition
1934+ stats := <- statsCh
1935+ require .Len (t , stats .Transitions , 1 , "lifecycle executor should transition the task workspace" )
1936+ assert .Contains (t , stats .Transitions , workspace .ID , "task workspace should be in transitions" )
1937+ assert .Equal (t , database .WorkspaceTransitionStop , stats .Transitions [workspace .ID ], "should autostop the workspace" )
1938+ require .Empty (t , stats .Errors , "should have no errors when managing task workspaces" )
1939+ })
1940+ }
0 commit comments