@@ -1641,6 +1641,77 @@ func TestAPI(t *testing.T) {
16411641 require .NotNil (t , response .Devcontainers [0 ].Container , "container should not be nil" )
16421642 })
16431643
1644+ // Verify that modifying a config file broadcasts the dirty status
1645+ // over websocket immediately.
1646+ t .Run ("FileWatcherDirtyBroadcast" , func (t * testing.T ) {
1647+ t .Parallel ()
1648+
1649+ ctx := testutil .Context (t , testutil .WaitShort )
1650+ configPath := "/workspace/project/.devcontainer/devcontainer.json"
1651+ fWatcher := newFakeWatcher (t )
1652+ fLister := & fakeContainerCLI {
1653+ containers : codersdk.WorkspaceAgentListContainersResponse {
1654+ Containers : []codersdk.WorkspaceAgentContainer {
1655+ {
1656+ ID : "container-id" ,
1657+ FriendlyName : "container-name" ,
1658+ Running : true ,
1659+ Labels : map [string ]string {
1660+ agentcontainers .DevcontainerLocalFolderLabel : "/workspace/project" ,
1661+ agentcontainers .DevcontainerConfigFileLabel : configPath ,
1662+ },
1663+ },
1664+ },
1665+ },
1666+ }
1667+
1668+ mClock := quartz .NewMock (t )
1669+ tickerTrap := mClock .Trap ().TickerFunc ("updaterLoop" )
1670+
1671+ api := agentcontainers .NewAPI (
1672+ slogtest .Make (t , nil ).Leveled (slog .LevelDebug ),
1673+ agentcontainers .WithContainerCLI (fLister ),
1674+ agentcontainers .WithWatcher (fWatcher ),
1675+ agentcontainers .WithClock (mClock ),
1676+ )
1677+ api .Start ()
1678+ defer api .Close ()
1679+
1680+ srv := httptest .NewServer (api .Routes ())
1681+ defer srv .Close ()
1682+
1683+ tickerTrap .MustWait (ctx ).MustRelease (ctx )
1684+ tickerTrap .Close ()
1685+
1686+ wsConn , resp , err := websocket .Dial (ctx , "ws" + strings .TrimPrefix (srv .URL , "http" )+ "/watch" , nil )
1687+ require .NoError (t , err )
1688+ if resp != nil && resp .Body != nil {
1689+ defer resp .Body .Close ()
1690+ }
1691+ defer wsConn .Close (websocket .StatusNormalClosure , "" )
1692+
1693+ // Read and discard initial state.
1694+ _ , _ , err = wsConn .Read (ctx )
1695+ require .NoError (t , err )
1696+
1697+ fWatcher .waitNext (ctx )
1698+ fWatcher .sendEventWaitNextCalled (ctx , fsnotify.Event {
1699+ Name : configPath ,
1700+ Op : fsnotify .Write ,
1701+ })
1702+
1703+ // Verify dirty status is broadcast without advancing the clock.
1704+ _ , msg , err := wsConn .Read (ctx )
1705+ require .NoError (t , err )
1706+
1707+ var response codersdk.WorkspaceAgentListContainersResponse
1708+ err = json .Unmarshal (msg , & response )
1709+ require .NoError (t , err )
1710+ require .Len (t , response .Devcontainers , 1 )
1711+ assert .True (t , response .Devcontainers [0 ].Dirty ,
1712+ "devcontainer should be marked as dirty after config file modification" )
1713+ })
1714+
16441715 t .Run ("SubAgentLifecycle" , func (t * testing.T ) {
16451716 t .Parallel ()
16461717
0 commit comments