generated from nhcarrigan/template
fix: refresh Discord avatar hash on every game load (#104)
Adds fetchDiscordUserById (bot token) to the Discord service and calls it in parallel with the DB queries on game load. When the returned hash differs from the stored value the Player record is updated and the hash is immediately synced into the returned game state, so the resource bar always shows the player's current Discord avatar. Also adds onError fallback: if the avatar URL is stale before the next load, the resource bar component now derives the URL fresh from state on every render rather than caching it.
This commit is contained in:
@@ -19,6 +19,10 @@ vi.mock("../../src/middleware/auth.js", () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("../../src/services/discord.js", () => ({
|
||||
fetchDiscordUserById: vi.fn().mockResolvedValue(null),
|
||||
}));
|
||||
|
||||
const DISCORD_ID = "test_discord_id";
|
||||
const CURRENT_SCHEMA_VERSION = 1;
|
||||
|
||||
@@ -200,6 +204,75 @@ describe("game route", () => {
|
||||
expect(body.offlineGold).toBeGreaterThan(0);
|
||||
expect(body.offlineEssence).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("syncs updated avatar from Discord into the returned state", async () => {
|
||||
const todayUTC = new Date().toISOString().slice(0, 10);
|
||||
const state = makeState();
|
||||
vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never);
|
||||
vi.mocked(prisma.player.findUnique).mockResolvedValueOnce(
|
||||
makePlayer({ lastLoginDate: todayUTC, avatar: "old_hash" }) as never,
|
||||
);
|
||||
vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never);
|
||||
vi.mocked(prisma.player.update).mockResolvedValueOnce({} as never);
|
||||
const { fetchDiscordUserById } = await import("../../src/services/discord.js");
|
||||
vi.mocked(fetchDiscordUserById).mockResolvedValueOnce({
|
||||
id: DISCORD_ID, username: "u", discriminator: "0", avatar: "new_hash",
|
||||
});
|
||||
const res = await app.fetch(new Request("http://localhost/game/load"));
|
||||
expect(res.status).toBe(200);
|
||||
const body = await res.json() as { state: GameState };
|
||||
expect(body.state.player.avatar).toBe("new_hash");
|
||||
});
|
||||
|
||||
it("continues loading when the avatar DB update fails", async () => {
|
||||
const todayUTC = new Date().toISOString().slice(0, 10);
|
||||
const state = makeState();
|
||||
vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never);
|
||||
vi.mocked(prisma.player.findUnique).mockResolvedValueOnce(
|
||||
makePlayer({ lastLoginDate: todayUTC, avatar: "old_hash" }) as never,
|
||||
);
|
||||
vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never);
|
||||
vi.mocked(prisma.player.update).mockRejectedValueOnce(new Error("db error"));
|
||||
const { fetchDiscordUserById } = await import("../../src/services/discord.js");
|
||||
vi.mocked(fetchDiscordUserById).mockResolvedValueOnce({
|
||||
id: DISCORD_ID, username: "u", discriminator: "0", avatar: "new_hash",
|
||||
});
|
||||
const res = await app.fetch(new Request("http://localhost/game/load"));
|
||||
expect(res.status).toBe(200);
|
||||
});
|
||||
|
||||
it("continues loading when the avatar DB update fails with a non-Error value", async () => {
|
||||
const todayUTC = new Date().toISOString().slice(0, 10);
|
||||
const state = makeState();
|
||||
vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never);
|
||||
vi.mocked(prisma.player.findUnique).mockResolvedValueOnce(
|
||||
makePlayer({ lastLoginDate: todayUTC, avatar: "old_hash" }) as never,
|
||||
);
|
||||
vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never);
|
||||
vi.mocked(prisma.player.update).mockRejectedValueOnce("raw string error");
|
||||
const { fetchDiscordUserById } = await import("../../src/services/discord.js");
|
||||
vi.mocked(fetchDiscordUserById).mockResolvedValueOnce({
|
||||
id: DISCORD_ID, username: "u", discriminator: "0", avatar: "new_hash",
|
||||
});
|
||||
const res = await app.fetch(new Request("http://localhost/game/load"));
|
||||
expect(res.status).toBe(200);
|
||||
});
|
||||
|
||||
it("keeps stored avatar when Discord returns null", async () => {
|
||||
const todayUTC = new Date().toISOString().slice(0, 10);
|
||||
const state = makeState();
|
||||
vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never);
|
||||
vi.mocked(prisma.player.findUnique).mockResolvedValueOnce(
|
||||
makePlayer({ lastLoginDate: todayUTC, avatar: "stored_hash" }) as never,
|
||||
);
|
||||
vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never);
|
||||
const { fetchDiscordUserById } = await import("../../src/services/discord.js");
|
||||
vi.mocked(fetchDiscordUserById).mockResolvedValueOnce(null);
|
||||
const res = await app.fetch(new Request("http://localhost/game/load"));
|
||||
expect(res.status).toBe(200);
|
||||
const body = await res.json() as { state: GameState };
|
||||
expect(body.state.player.avatar).toBe("stored_hash");
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /save", () => {
|
||||
|
||||
Reference in New Issue
Block a user