mirror of
https://github.com/wu736139669/hapi.git
synced 2026-10-10 19:42:21 +00:00
ios/scripts/linux-test.sh stages HapiKit + shared/fixtures at repo depth
into a persistent tmp dir and runs swift test in swift:6.1-noble; the
manifest drops HapiUI (SwiftUI/swift-markdown/Highlightr) under
#if os(Linux). 458 tests green, including all 48 chat and 11 pagination
golden fixtures.
Fixes that fell out of the first real compile of the blind-written port:
- ChatTypes.CodexReview.wireValue + SummaryPatching legacy id fallback:
split expressions that exceeded the Swift 6 type-checker budget
- FileEndpointsTests: raw string containing "# terminated the literal
early (never compiled anywhere) — now ##-delimited
- SSEClient.backoffSleep: Task { try? ... } inferred Task<()?, Never>
- Darwin gates: Security/Keychain behind canImport(Security) (tests use
InMemoryCredentialStore via CredentialStoring), CryptoKit digest with
FNV-1a filename fallback, FoundationNetworking imports for URLSession
types, corelibs URLCache diskPath: initializer, get-only
waitsForConnectivity, delegate-based SSE transport where
URLSession.bytes(for:) does not exist
141 lines
6.0 KiB
Swift
141 lines
6.0 KiB
Swift
import Foundation
|
|
import HapiClient
|
|
import HapiProtocol
|
|
import Testing
|
|
|
|
/// Request construction + response decoding of the git/files endpoints
|
|
/// (A-M4a) against the recording performer, mirroring the Android
|
|
/// `HapiApi` git & files section and `docs/api/client-contract/rest.md`.
|
|
@Suite("File endpoint requests")
|
|
struct FileEndpointsTests {
|
|
|
|
@Test func gitStatusBuildsPathAndDecodesCommandResponse() async throws {
|
|
let harness = try makeHarness(jwt: freshJWT())
|
|
await harness.performer.enqueue(
|
|
// ##-delimited: the payload contains `"#`, which would terminate
|
|
// a single-# raw string early.
|
|
json: ##"{"success":true,"stdout":"# branch.head main\n","exitCode":0}"##
|
|
)
|
|
|
|
let response = try await harness.client.gitStatus(sessionId: "s 1")
|
|
#expect(response.success)
|
|
#expect(response.stdout == "# branch.head main\n")
|
|
#expect(response.exitCode == 0)
|
|
|
|
let request = await harness.performer.requests.first
|
|
#expect(request?.url?.absoluteString == "\(testHubURLString)/api/sessions/s%201/git-status")
|
|
#expect(request?.httpMethod == "GET")
|
|
}
|
|
|
|
@Test func gitDiffNumstatCarriesStagedFlag() async throws {
|
|
let harness = try makeHarness(jwt: freshJWT())
|
|
await harness.performer.enqueue(json: #"{"success":true,"stdout":""}"#)
|
|
await harness.performer.enqueue(json: #"{"success":true,"stdout":""}"#)
|
|
|
|
_ = try await harness.client.gitDiffNumstat(sessionId: "abc", staged: true)
|
|
_ = try await harness.client.gitDiffNumstat(sessionId: "abc", staged: false)
|
|
|
|
let requests = await harness.performer.requests
|
|
#expect(
|
|
requests.first?.url?.absoluteString
|
|
== "\(testHubURLString)/api/sessions/abc/git-diff-numstat?staged=true"
|
|
)
|
|
#expect(
|
|
requests.last?.url?.absoluteString
|
|
== "\(testHubURLString)/api/sessions/abc/git-diff-numstat?staged=false"
|
|
)
|
|
}
|
|
|
|
@Test func gitDiffFileEncodesPathAndOptionalStaged() async throws {
|
|
let harness = try makeHarness(jwt: freshJWT())
|
|
await harness.performer.enqueue(json: #"{"success":true,"stdout":""}"#)
|
|
await harness.performer.enqueue(json: #"{"success":false,"error":"boom"}"#)
|
|
|
|
_ = try await harness.client.gitDiffFile(sessionId: "abc", path: "src/app.ts", staged: false)
|
|
let second = try await harness.client.gitDiffFile(sessionId: "abc", path: "src/app.ts")
|
|
#expect(second.success == false)
|
|
#expect(second.error == "boom")
|
|
|
|
let requests = await harness.performer.requests
|
|
#expect(
|
|
requests.first?.url?.absoluteString
|
|
== "\(testHubURLString)/api/sessions/abc/git-diff-file?path=src%2Fapp.ts&staged=false"
|
|
)
|
|
// Omitted staged flag stays off the URL (unstaged side).
|
|
#expect(
|
|
requests.last?.url?.absoluteString
|
|
== "\(testHubURLString)/api/sessions/abc/git-diff-file?path=src%2Fapp.ts"
|
|
)
|
|
}
|
|
|
|
@Test func readSessionFileEncodesPathStrictly() async throws {
|
|
let harness = try makeHarness(jwt: freshJWT())
|
|
await harness.performer.enqueue(
|
|
json: #"{"success":true,"content":"aGk=","size":2,"modified":1700000000000}"#
|
|
)
|
|
|
|
let response = try await harness.client.readSessionFile(sessionId: "abc", path: "a b+c.txt")
|
|
#expect(response.content == "aGk=")
|
|
#expect(response.size == 2)
|
|
#expect(response.modified == 1_700_000_000_000)
|
|
|
|
let request = await harness.performer.requests.first
|
|
#expect(
|
|
request?.url?.absoluteString
|
|
== "\(testHubURLString)/api/sessions/abc/file?path=a%20b%2Bc.txt"
|
|
)
|
|
}
|
|
|
|
@Test func searchSessionFilesOmitsEmptyQueryAndNilLimit() async throws {
|
|
let harness = try makeHarness(jwt: freshJWT())
|
|
await harness.performer.enqueue(json: """
|
|
{"success":true,"files":[{"fileName":"app.ts","filePath":"src",\
|
|
"fullPath":"src/app.ts","fileType":"file","size":10}]}
|
|
""")
|
|
await harness.performer.enqueue(json: #"{"success":true,"files":[]}"#)
|
|
|
|
let response = try await harness.client.searchSessionFiles(
|
|
sessionId: "abc",
|
|
query: "app",
|
|
limit: 200
|
|
)
|
|
#expect(response.files?.map(\.fullPath) == ["src/app.ts"])
|
|
_ = try await harness.client.searchSessionFiles(sessionId: "abc", query: "", limit: nil)
|
|
|
|
let requests = await harness.performer.requests
|
|
#expect(
|
|
requests.first?.url?.absoluteString
|
|
== "\(testHubURLString)/api/sessions/abc/files?query=app&limit=200"
|
|
)
|
|
#expect(requests.last?.url?.absoluteString == "\(testHubURLString)/api/sessions/abc/files")
|
|
}
|
|
|
|
@Test func listSessionDirectoryOmitsRootPathAndDecodesEntryTypes() async throws {
|
|
let harness = try makeHarness(jwt: freshJWT())
|
|
await harness.performer.enqueue(json: """
|
|
{"success":true,"entries":[{"name":"src","type":"directory"},\
|
|
{"name":"main.ts","type":"file","size":5,"modified":123},\
|
|
{"name":"sock","type":"other"}]}
|
|
""")
|
|
await harness.performer.enqueue(json: #"{"success":false,"error":"denied"}"#)
|
|
|
|
let root = try await harness.client.listSessionDirectory(sessionId: "abc")
|
|
#expect(root.entries == [
|
|
DirectoryEntry(name: "src", type: .directory),
|
|
DirectoryEntry(name: "main.ts", type: .file, size: 5, modified: 123),
|
|
DirectoryEntry(name: "sock", type: .other),
|
|
])
|
|
|
|
let nested = try await harness.client.listSessionDirectory(sessionId: "abc", path: "src/app")
|
|
#expect(nested.success == false)
|
|
#expect(nested.error == "denied")
|
|
|
|
let requests = await harness.performer.requests
|
|
#expect(requests.first?.url?.absoluteString == "\(testHubURLString)/api/sessions/abc/directory")
|
|
#expect(
|
|
requests.last?.url?.absoluteString
|
|
== "\(testHubURLString)/api/sessions/abc/directory?path=src%2Fapp"
|
|
)
|
|
}
|
|
}
|