mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix(demo): populate admin data, fix settings crash and mobile editor icon (#463)
Rewrite the demo mock API around a seeded in-memory dataset with correct response shapes so the People/Teams/Roles/Audit/Usage/API-keys tabs stop crashing (the /auth/users vs /v1/users mismatch caused users.filter() on undefined) and show realistic sample data. In-memory CRUD makes the settings buttons work. Copy edit-image.png into the demo so the mobile editor icon renders. Adds unit shape guards and an e2e admin-settings walkthrough.
This commit is contained in:
@@ -35,4 +35,119 @@ describe("demo mock API", () => {
|
||||
ssoEnforced: false,
|
||||
});
|
||||
});
|
||||
|
||||
// These lock in the exact response shapes the admin settings screens read.
|
||||
// A missing array here is what produced the "can't access property filter"
|
||||
// crash: the People tab calls /auth/users, and the mock used to answer only
|
||||
// /v1/users, so `data.users` was undefined and `users.filter()` threw.
|
||||
|
||||
it("serves the People list at /auth/users with a populated users array", async () => {
|
||||
const response = matchDemoRoute("/api/auth/users", "GET");
|
||||
expect(response?.status).toBe(200);
|
||||
const data = (await readJson(response as Response)) as {
|
||||
users: Array<Record<string, unknown>>;
|
||||
maxUsers: number;
|
||||
};
|
||||
expect(Array.isArray(data.users)).toBe(true);
|
||||
expect(data.users.length).toBeGreaterThan(3);
|
||||
expect(typeof data.maxUsers).toBe("number");
|
||||
for (const user of data.users) {
|
||||
expect(typeof user.username).toBe("string");
|
||||
expect(typeof user.role).toBe("string");
|
||||
expect(typeof user.team).toBe("string");
|
||||
expect(typeof user.createdAt).toBe("string");
|
||||
}
|
||||
});
|
||||
|
||||
it("serves teams, roles, and api keys as real arrays", async () => {
|
||||
const teams = (await readJson(matchDemoRoute("/api/v1/teams", "GET") as Response)) as {
|
||||
teams: unknown[];
|
||||
};
|
||||
const roles = (await readJson(matchDemoRoute("/api/v1/roles", "GET") as Response)) as {
|
||||
roles: unknown[];
|
||||
};
|
||||
const keys = (await readJson(matchDemoRoute("/api/v1/api-keys", "GET") as Response)) as {
|
||||
apiKeys: unknown[];
|
||||
};
|
||||
expect(Array.isArray(teams.teams)).toBe(true);
|
||||
expect(teams.teams.length).toBeGreaterThan(0);
|
||||
expect(Array.isArray(roles.roles)).toBe(true);
|
||||
expect(roles.roles.length).toBeGreaterThan(0);
|
||||
expect(Array.isArray(keys.apiKeys)).toBe(true);
|
||||
expect(keys.apiKeys.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("paginates the audit log and honours the action filter", async () => {
|
||||
const page1 = (await readJson(
|
||||
matchDemoRoute("/api/v1/audit-log?page=1&limit=25", "GET") as Response,
|
||||
)) as { entries: unknown[]; total: number };
|
||||
expect(Array.isArray(page1.entries)).toBe(true);
|
||||
expect(page1.entries.length).toBeGreaterThan(0);
|
||||
expect(page1.entries.length).toBeLessThanOrEqual(25);
|
||||
expect(page1.total).toBeGreaterThanOrEqual(page1.entries.length);
|
||||
|
||||
const filtered = (await readJson(
|
||||
matchDemoRoute("/api/v1/audit-log?page=1&limit=25&action=LOGIN_SUCCESS", "GET") as Response,
|
||||
)) as { entries: Array<{ action: string }> };
|
||||
for (const entry of filtered.entries) {
|
||||
expect(entry.action).toBe("LOGIN_SUCCESS");
|
||||
}
|
||||
});
|
||||
|
||||
it("returns a complete usage payload with every array the dashboard maps", async () => {
|
||||
const usage = (await readJson(
|
||||
matchDemoRoute("/api/v1/admin/usage?days=30", "GET") as Response,
|
||||
)) as Record<string, unknown>;
|
||||
for (const key of ["jobsPerDay", "topTools", "perUser", "durations", "teamStorage"]) {
|
||||
expect(Array.isArray(usage[key])).toBe(true);
|
||||
expect((usage[key] as unknown[]).length).toBeGreaterThan(0);
|
||||
}
|
||||
const storage = usage.storage as { libraryBytes: string; libraryFiles: number };
|
||||
expect(typeof storage.libraryBytes).toBe("string");
|
||||
expect(typeof storage.libraryFiles).toBe("number");
|
||||
});
|
||||
|
||||
it("exposes settings and preferences objects the settings tabs read", async () => {
|
||||
const settings = (await readJson(matchDemoRoute("/api/v1/settings", "GET") as Response)) as {
|
||||
settings: Record<string, string>;
|
||||
};
|
||||
expect(settings.settings.fileUploadLimitMb).toBeTruthy();
|
||||
expect(settings.settings.passwordMinLength).toBeTruthy();
|
||||
const prefs = (await readJson(matchDemoRoute("/api/v1/preferences", "GET") as Response)) as {
|
||||
preferences: Record<string, unknown>;
|
||||
};
|
||||
expect(typeof prefs.preferences).toBe("object");
|
||||
});
|
||||
|
||||
it("creates an API key and returns the one-time raw key", async () => {
|
||||
const response = matchDemoRoute(
|
||||
"/api/v1/api-keys",
|
||||
"POST",
|
||||
JSON.stringify({ name: "Demo test key" }),
|
||||
);
|
||||
expect(response?.status).toBe(200);
|
||||
const data = (await readJson(response as Response)) as { key: string; name: string };
|
||||
expect(data.name).toBe("Demo test key");
|
||||
expect(data.key.startsWith("si_")).toBe(true);
|
||||
});
|
||||
|
||||
it("adds a user via /auth/register so the People tab reload shows it", async () => {
|
||||
const created = matchDemoRoute(
|
||||
"/api/auth/register",
|
||||
"POST",
|
||||
JSON.stringify({ username: "test.newbie", role: "user", team: "Design" }),
|
||||
);
|
||||
expect(created?.status).toBe(200);
|
||||
const list = (await readJson(matchDemoRoute("/api/auth/users", "GET") as Response)) as {
|
||||
users: Array<{ username: string }>;
|
||||
};
|
||||
expect(list.users.some((u) => u.username === "test.newbie")).toBe(true);
|
||||
});
|
||||
|
||||
it("disables file processing with a clear demo message", async () => {
|
||||
const response = matchDemoRoute("/api/v1/tools/compress-image", "POST", "{}");
|
||||
expect(response?.status).toBe(403);
|
||||
const data = (await readJson(response as Response)) as { error: string };
|
||||
expect(data.error).toContain("demo");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user