Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions packages/raiderio/src/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type FixtureName =
| "character-renamed-root"
| "profile-valid"
| "profile-invalid"
| "profile-forbidden"
| "claimed-characters"
| "claimed-characters-out-of-scope"
| "missing-character"
Expand Down Expand Up @@ -45,6 +46,7 @@ function fixtureFetch(name: FixtureName): typeof globalThis.fetch {
const expectsProfile =
name === "profile-valid" ||
name === "profile-invalid" ||
name === "profile-forbidden" ||
name === "claimed-characters" ||
name === "claimed-characters-out-of-scope";
const expectedPath = expectsProfile
Expand Down Expand Up @@ -289,6 +291,15 @@ describe("Raider.IO gateway", () => {
).resolves.toBeNull();
});

it("treats a private user profile as no profile, not an outage", async () => {
// Break caught: Raider.IO answers 403 profile_is_private for a guessed user
// name. Classifying that as transient retried a permanent answer and failed
// the whole run as upstream_unavailable.
await expect(
clientFor("profile-forbidden").resolveProfileGuess("private-user")
).resolves.toBeNull();
});

it("classifies a missing character", async () => {
await expect(
clientFor("missing-character").getCharacter(sentinel)
Expand Down
11 changes: 10 additions & 1 deletion packages/raiderio/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ function retryAfterMs(response: Response): number | undefined {

function responseFailure(response: Response): RaiderIoFailure {
if (response.status === 404) return { kind: "not_found" };
// Raider.IO answers 403 for a user profile its owner has made private. That
// is a permanent answer about visibility, not an outage, so it must never be
// retried as one.
if (response.status === 403) return { kind: "forbidden" };

const retryAfter = retryAfterMs(response);
return {
Expand Down Expand Up @@ -172,7 +176,12 @@ export function createRaiderIoClient(
...(profile.omittedMembers ? { omittedMembers: true } : {})
};
} catch (error) {
if (isRaiderIoFailure(error) && error.kind === "not_found") return null;
if (
isRaiderIoFailure(error) &&
(error.kind === "not_found" || error.kind === "forbidden")
) {
return null;
}
throw error;
}
}
Expand Down
2 changes: 2 additions & 0 deletions packages/raiderio/src/errors.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export type RaiderIoFailure =
| { kind: "not_found" }
| { kind: "forbidden" }
| {
kind: "transient";
status?: number;
Expand All @@ -14,6 +15,7 @@ export function isRaiderIoFailure(value: unknown): value is RaiderIoError {

return (
value.kind === "not_found" ||
value.kind === "forbidden" ||
value.kind === "transient" ||
value.kind === "schema_drift"
);
Expand Down
9 changes: 9 additions & 0 deletions tests/fixtures/raiderio/profile-forbidden.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"status": 403,
"body": {
"statusCode": 403,
"error": "Forbidden",
"message": "The requested user's profile is private and cannot be viewed.",
"errorCode": "profile_is_private"
}
}
Loading