fix(shares): shares.info wraps the share in {shares: [...]}, not bare
Empty-body handling fixed "no share yet" but re-opening the share
sheet for a document that already had one still errored — confirmed
via a raw response capture that data is { shares: [...] }, a
one-element array, not the bare share object the docs show. Same
pattern as pins.list.
Added SharesInfoPayload (mirrors PinsListPayload), shareInfo now takes
.shares.first. createShare/updateShare are unaffected — the user's
earlier successful create-and-copy confirms those aren't wrapped this
way, only shares.info. Test rewritten with the exact captured payload.
This commit is contained in:
@@ -121,7 +121,13 @@ public actor LiveOutlineAPIClient: OutlineAPIClient {
|
||||
}
|
||||
|
||||
public func shareInfo(documentId: String) async throws -> OutlineShare? {
|
||||
try await postOptional("shares.info", body: ShareInfoRequest(documentId: documentId))
|
||||
// Real shape confirmed against a live server: `data` is
|
||||
// `{ shares: [...] }`, not the bare share object the docs imply —
|
||||
// same pattern as `pins.list`. A document could in principle have
|
||||
// more than one share record; the first is what the reader's
|
||||
// share sheet cares about.
|
||||
let payload: SharesInfoPayload? = try await postOptional("shares.info", body: ShareInfoRequest(documentId: documentId))
|
||||
return payload?.shares.first
|
||||
}
|
||||
|
||||
public func updateShare(_ request: UpdateShareRequest) async throws -> OutlineShare {
|
||||
@@ -354,6 +360,10 @@ private struct PinsListPayload: Decodable {
|
||||
let documents: [OutlineDocument]
|
||||
}
|
||||
|
||||
private struct SharesInfoPayload: Decodable {
|
||||
let shares: [OutlineShare]
|
||||
}
|
||||
|
||||
private struct ListStarsResponse: Decodable {
|
||||
let stars: [OutlineStar]
|
||||
}
|
||||
|
||||
@@ -669,54 +669,59 @@ final class LiveOutlineAPIClientTests: XCTestCase {
|
||||
XCTAssertEqual(httpClient.lastRequest?.url?.path, "/api/shares.create")
|
||||
}
|
||||
|
||||
func testShareInfoDecodesFullDocumentedShape() async throws {
|
||||
// Matches the exact response shape from Outline's official
|
||||
// shares.info docs, including every field the hosted app can send —
|
||||
// the model must tolerate all of this without throwing.
|
||||
func testShareInfoDecodesRealShareArrayShape() async throws {
|
||||
// Real shape confirmed against a live server — `data` is
|
||||
// `{ shares: [...] }`, not the bare share object the official docs
|
||||
// imply (same pattern as `pins.list`). This is the exact payload
|
||||
// captured for an existing, unpublished share.
|
||||
let httpClient = MockHTTPClient()
|
||||
httpClient.responseData = """
|
||||
{
|
||||
"data": {
|
||||
"id": "123e4567-e89b-12d3-a456-426614174000",
|
||||
"documentTitle": "React best practices",
|
||||
"documentUrl": "https://example.com",
|
||||
"sourceTitle": "string",
|
||||
"sourcePath": "string",
|
||||
"documentId": null,
|
||||
"collectionId": null,
|
||||
"urlId": null,
|
||||
"url": "https://example.com",
|
||||
"domain": null,
|
||||
"title": null,
|
||||
"iconUrl": null,
|
||||
"published": false,
|
||||
"includeChildDocuments": true,
|
||||
"allowSubscriptions": true,
|
||||
"allowIndexing": true,
|
||||
"showLastUpdated": true,
|
||||
"showTOC": true,
|
||||
"views": 1,
|
||||
"createdAt": "2026-08-12T17:41:28.333Z",
|
||||
"createdBy": {
|
||||
"id": "123e4567-e89b-12d3-a456-426614174000",
|
||||
"name": "Jane Doe",
|
||||
"avatarUrl": "https://example.com",
|
||||
"color": "string",
|
||||
"email": "hello@example.com",
|
||||
"role": "admin",
|
||||
"isSuspended": true,
|
||||
"lastActiveAt": null,
|
||||
"timezone": null,
|
||||
"createdAt": "2026-08-12T17:41:28.333Z",
|
||||
"updatedAt": "2026-08-12T17:41:28.333Z",
|
||||
"deletedAt": null
|
||||
},
|
||||
"updatedAt": "2026-08-12T17:41:28.333Z",
|
||||
"lastAccessedAt": null
|
||||
"shares": [
|
||||
{
|
||||
"id": "861559ac-906b-4dec-9c1f-3a2d2000745d",
|
||||
"sourceTitle": "Test Document 3",
|
||||
"sourcePath": "/doc/test-document-3-VHxABl5RaD",
|
||||
"collectionId": null,
|
||||
"documentId": "b1196971-4239-4e35-970f-7fbbc60af044",
|
||||
"documentTitle": "Test Document 3",
|
||||
"documentUrl": "/doc/test-document-3-VHxABl5RaD",
|
||||
"published": false,
|
||||
"url": "https://docs.psmattas.com/s/861559ac-906b-4dec-9c1f-3a2d2000745d",
|
||||
"urlId": null,
|
||||
"createdBy": {
|
||||
"id": "1e2ef39c-aa82-475b-b5af-d76bb4f023ed",
|
||||
"name": "Puranjay Savar Mattas",
|
||||
"avatarUrl": "/api/files.get?key=public/avatar.png",
|
||||
"color": "#1c9152",
|
||||
"role": "admin",
|
||||
"isSuspended": false,
|
||||
"createdAt": "2025-07-30T17:36:58.560Z",
|
||||
"updatedAt": "2026-08-14T16:47:45.037Z",
|
||||
"deletedAt": null,
|
||||
"lastActiveAt": "2026-08-14T16:47:45.037Z",
|
||||
"timezone": "Europe/Dublin"
|
||||
},
|
||||
"includeChildDocuments": false,
|
||||
"allowIndexing": false,
|
||||
"allowSubscriptions": true,
|
||||
"showLastUpdated": false,
|
||||
"showTOC": false,
|
||||
"title": null,
|
||||
"iconUrl": null,
|
||||
"views": 0,
|
||||
"domain": null,
|
||||
"createdAt": "2026-08-14T16:49:40.146Z",
|
||||
"updatedAt": "2026-08-14T16:49:40.146Z"
|
||||
}
|
||||
]
|
||||
},
|
||||
"policies": [
|
||||
{ "id": "123e4567-e89b-12d3-a456-426614174000", "abilities": { "read": true, "update": true, "delete": false } }
|
||||
]
|
||||
{ "id": "861559ac-906b-4dec-9c1f-3a2d2000745d", "abilities": { "read": true, "update": false, "revoke": true } }
|
||||
],
|
||||
"status": 200,
|
||||
"ok": true
|
||||
}
|
||||
""".data(using: .utf8)!
|
||||
|
||||
@@ -728,10 +733,11 @@ final class LiveOutlineAPIClientTests: XCTestCase {
|
||||
|
||||
let share = try await client.shareInfo(documentId: "doc-1")
|
||||
|
||||
XCTAssertEqual(share?.id, "861559ac-906b-4dec-9c1f-3a2d2000745d")
|
||||
XCTAssertEqual(share?.published, false)
|
||||
XCTAssertEqual(share?.views, 1)
|
||||
XCTAssertEqual(share?.createdBy?.name, "Jane Doe")
|
||||
XCTAssertNil(share?.documentId)
|
||||
XCTAssertEqual(share?.views, 0)
|
||||
XCTAssertEqual(share?.createdBy?.name, "Puranjay Savar Mattas")
|
||||
XCTAssertEqual(share?.documentId, "b1196971-4239-4e35-970f-7fbbc60af044")
|
||||
}
|
||||
|
||||
func testListSharesDecodesSharesArray() async throws {
|
||||
|
||||
Reference in New Issue
Block a user