Release v0.1.6 2026-07-12
This commit is contained in:
@@ -1,53 +1,38 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { StorageAdapter } from "./saveRepository";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { AccountRepository } from "./accountRepository";
|
||||
import { OnlineApiError, type OnlineRepository } from "./onlineRepository";
|
||||
|
||||
function memoryStorage(): StorageAdapter {
|
||||
const data = new Map<string, string>();
|
||||
function onlineStub(overrides: Partial<OnlineRepository> = {}): OnlineRepository {
|
||||
return {
|
||||
getItem: (key) => data.get(key) ?? null,
|
||||
setItem: (key, value) => { data.set(key, value); },
|
||||
};
|
||||
register: vi.fn(async (username: string) => ({ id: 1, username })),
|
||||
login: vi.fn(async (username: string) => ({ id: 1, username })),
|
||||
session: vi.fn(async () => null),
|
||||
logout: vi.fn(async () => undefined),
|
||||
...overrides,
|
||||
} as unknown as OnlineRepository;
|
||||
}
|
||||
|
||||
const testHasher = async (password: string, salt: string) => {
|
||||
const checksum = [...password].reduce((total, character) => total + character.charCodeAt(0), 0);
|
||||
return `derived:${salt}:${checksum}`;
|
||||
};
|
||||
|
||||
describe("AccountRepository", () => {
|
||||
it("requires both a username and password", async () => {
|
||||
const repository = new AccountRepository(memoryStorage(), testHasher, () => "salt");
|
||||
|
||||
it("requires both username and password before contacting server", async () => {
|
||||
const online = onlineStub();
|
||||
const repository = new AccountRepository(online);
|
||||
await expect(repository.create("", "secret")).resolves.toEqual({ ok: false, reason: "missing-credentials" });
|
||||
await expect(repository.create("healer", "")).resolves.toEqual({ ok: false, reason: "missing-credentials" });
|
||||
await expect(repository.authenticate("healer", "")).resolves.toEqual({ ok: false, reason: "missing-credentials" });
|
||||
expect(online.register).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("requires account creation before sign-in", async () => {
|
||||
const repository = new AccountRepository(memoryStorage(), testHasher, () => "salt");
|
||||
|
||||
await expect(repository.authenticate("new-healer", "secret")).resolves.toEqual({ ok: false, reason: "account-not-found" });
|
||||
await expect(repository.create("new-healer", "secret")).resolves.toEqual({ ok: true, username: "new-healer" });
|
||||
await expect(repository.authenticate("new-healer", "secret")).resolves.toEqual({ ok: true, username: "new-healer" });
|
||||
it("creates and authenticates real server accounts", async () => {
|
||||
const repository = new AccountRepository(onlineStub());
|
||||
await expect(repository.create("Wayfinder", "long-password")).resolves.toEqual({ ok: true, username: "Wayfinder" });
|
||||
await expect(repository.authenticate("Wayfinder", "long-password")).resolves.toEqual({ ok: true, username: "Wayfinder" });
|
||||
});
|
||||
|
||||
it("rejects an incorrect password and duplicate account names", async () => {
|
||||
const repository = new AccountRepository(memoryStorage(), testHasher, () => "salt");
|
||||
await repository.create("Wayfinder", "correct");
|
||||
|
||||
await expect(repository.authenticate("wayfinder", "wrong")).resolves.toEqual({ ok: false, reason: "invalid-password" });
|
||||
await expect(repository.create(" wayfinder ", "another")).resolves.toEqual({ ok: false, reason: "account-exists" });
|
||||
});
|
||||
|
||||
it("persists only a derived password verifier", async () => {
|
||||
const storage = memoryStorage();
|
||||
const repository = new AccountRepository(storage, testHasher, () => "unique-salt");
|
||||
await repository.create("healer", "plaintext-secret");
|
||||
|
||||
const persisted = storage.getItem("i-want-to-heal:accounts:v1") ?? "";
|
||||
expect(persisted).toContain("derived:unique-salt:");
|
||||
expect(persisted).not.toContain("plaintext-secret");
|
||||
expect(JSON.parse(persisted).healer).not.toHaveProperty("password");
|
||||
it("maps server conflicts, invalid credentials, and outages", async () => {
|
||||
const conflict = new AccountRepository(onlineStub({ register: vi.fn(async () => { throw new OnlineApiError("exists", 409); }) }));
|
||||
await expect(conflict.create("Wayfinder", "long-password")).resolves.toMatchObject({ ok: false, reason: "account-exists" });
|
||||
const invalid = new AccountRepository(onlineStub({ login: vi.fn(async () => { throw new OnlineApiError("bad login", 401); }) }));
|
||||
await expect(invalid.authenticate("Wayfinder", "wrong-password")).resolves.toMatchObject({ ok: false, reason: "invalid-password" });
|
||||
const outage = new AccountRepository(onlineStub({ login: vi.fn(async () => { throw new OnlineApiError("offline", 0); }) }));
|
||||
await expect(outage.authenticate("Wayfinder", "long-password")).resolves.toMatchObject({ ok: false, reason: "server-unavailable" });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user