@@ -125,6 +125,43 @@ struct OpenCodeGoUsageFetcherCLIWaitTests {
125125 #expect( rootTimeout == 60 )
126126 }
127127
128+ @Test
129+ func `cli wait policy bounds optional balance wait from task start`() async throws {
130+ defer {
131+ OpenCodeGoCLIWaitStubURLProtocol . handler = nil
132+ OpenCodeGoCLIWaitStubURLProtocol . hangPaths = [ ]
133+ OpenCodeGoCLIWaitStubURLProtocol . delayedPaths = [ : ]
134+ }
135+
136+ OpenCodeGoCLIWaitStubURLProtocol . hangPaths = [ " /workspace/wrk_TEST123 " ]
137+ OpenCodeGoCLIWaitStubURLProtocol . delayedPaths = [ " /workspace/wrk_TEST123/go " : 2 ]
138+ OpenCodeGoCLIWaitStubURLProtocol . handler = { request in
139+ guard let url = request. url else { throw URLError ( . badURL) }
140+ return Self . makeResponse (
141+ url: url,
142+ body: Self . goUsagePageHTML (
143+ workspaceID: " wrk_TEST123 " ,
144+ rolling: UsageWindow ( percent: 17 , resetInSec: 600 ) ,
145+ weekly: UsageWindow ( percent: 75 , resetInSec: 7200 ) ,
146+ monthly: nil ) ,
147+ statusCode: 200 ,
148+ contentType: " text/html " )
149+ }
150+
151+ let start = ContinuousClock . now
152+ let snapshot = try await OpenCodeGoUsageFetcher . fetchUsage (
153+ cookieHeader: " auth=test " ,
154+ timeout: 60 ,
155+ workspaceIDOverride: " wrk_TEST123 " ,
156+ waitForZenBalance: true ,
157+ session: self . makeSession ( ) )
158+ let elapsed = start. duration ( to: ContinuousClock . now)
159+
160+ #expect( snapshot. rollingUsagePercent == 17 )
161+ #expect( snapshot. zenBalanceUSD == nil )
162+ #expect( elapsed < . seconds( 6 ) )
163+ }
164+
128165 private static func goUsagePageHTML(
129166 workspaceID: String ,
130167 rolling: UsageWindow ,
@@ -173,13 +210,25 @@ struct OpenCodeGoUsageFetcherCLIWaitTests {
173210 }
174211}
175212
176- private final class OpenCodeGoCLIWaitStubURLProtocol : URLProtocol {
213+ private final class OpenCodeGoCLIWaitStubURLProtocol : URLProtocol , @ unchecked Sendable {
177214 private static let handlerBox = LockIsolated < ( ( URLRequest ) throws -> ( HTTPURLResponse , Data ) ) ? > ( nil )
178215 static var handler : ( ( URLRequest ) throws -> ( HTTPURLResponse , Data ) ) ? {
179216 get { Self . handlerBox. value }
180217 set { Self . handlerBox. setValue ( newValue) }
181218 }
182219
220+ private static let hangPathsBox = LockIsolated < Set < String > > ( [ ] )
221+ static var hangPaths : Set < String > {
222+ get { hangPathsBox. value }
223+ set { hangPathsBox. setValue ( newValue) }
224+ }
225+
226+ private static let delayedPathsBox = LockIsolated < [ String : TimeInterval ] > ( [ : ] )
227+ static var delayedPaths : [ String : TimeInterval ] {
228+ get { delayedPathsBox. value }
229+ set { delayedPathsBox. setValue ( newValue) }
230+ }
231+
183232 override static func canInit( with request: URLRequest ) -> Bool {
184233 request. url? . host == " opencode.ai "
185234 }
@@ -189,18 +238,37 @@ private final class OpenCodeGoCLIWaitStubURLProtocol: URLProtocol {
189238 }
190239
191240 override func startLoading( ) {
192- guard let handler = Self . handler else {
193- self . client? . urlProtocol ( self , didFailWithError: URLError ( . badServerResponse) )
241+ guard let url = self . request. url else {
242+ self . client? . urlProtocol ( self , didFailWithError: URLError ( . badURL) )
243+ return
244+ }
245+ if Self . hangPaths. contains ( url. path) {
194246 return
195247 }
196- do {
197- let ( response, data) = try handler ( self . request)
198- self . client? . urlProtocol ( self , didReceive: response, cacheStoragePolicy: . notAllowed)
199- self . client? . urlProtocol ( self , didLoad: data)
200- self . client? . urlProtocolDidFinishLoading ( self )
201- } catch {
202- self . client? . urlProtocol ( self , didFailWithError: error)
248+ let delay = Self . delayedPaths [ url. path] ?? 0
249+ let deliver : ( ) -> Void = { [ weak self] in
250+ guard let self else { return }
251+ do {
252+ let ( response, data) = try Self . response ( for: self . request)
253+ self . client? . urlProtocol ( self , didReceive: response, cacheStoragePolicy: . notAllowed)
254+ self . client? . urlProtocol ( self , didLoad: data)
255+ self . client? . urlProtocolDidFinishLoading ( self )
256+ } catch {
257+ self . client? . urlProtocol ( self , didFailWithError: error)
258+ }
259+ }
260+ if delay > 0 {
261+ DispatchQueue . global ( ) . asyncAfter ( deadline: . now( ) + delay, execute: deliver)
262+ } else {
263+ deliver ( )
264+ }
265+ }
266+
267+ private static func response( for request: URLRequest ) throws -> ( HTTPURLResponse , Data ) {
268+ guard let handler = Self . handler else {
269+ throw URLError ( . badServerResponse)
203270 }
271+ return try handler ( request)
204272 }
205273
206274 override func stopLoading( ) { }
0 commit comments