135 lines
5.0 KiB
TypeScript
135 lines
5.0 KiB
TypeScript
import { hashToken } from "@minecraft-account-manager/auth";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const databaseState = vi.hoisted(() => ({
|
|
account: { id: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", userId: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" } as { id: string; userId: string } | null,
|
|
inserts: [] as Record<string, unknown>[],
|
|
credentialHash: "" as string | null,
|
|
replay: false,
|
|
}));
|
|
|
|
vi.mock("@/lib/database", () => ({
|
|
db: {
|
|
select: () => ({
|
|
from: () => ({
|
|
where: () => ({
|
|
limit: async () => databaseState.credentialHash ? [{ secretHash: databaseState.credentialHash }] : [],
|
|
}),
|
|
}),
|
|
}),
|
|
transaction: async (callback: (tx: unknown) => Promise<unknown>) => callback({
|
|
delete: () => ({ where: async () => undefined }),
|
|
insert: () => ({
|
|
values: async (value: Record<string, unknown>) => {
|
|
if (databaseState.replay && "requestId" in value) {
|
|
throw { code: "23505", constraint_name: "plugin_requests_pkey" };
|
|
}
|
|
databaseState.inserts.push(value);
|
|
},
|
|
}),
|
|
select: () => ({
|
|
from: () => ({
|
|
where: () => ({
|
|
limit: async () => databaseState.account ? [databaseState.account] : [],
|
|
}),
|
|
}),
|
|
}),
|
|
}),
|
|
},
|
|
}));
|
|
|
|
import { GET, POST } from "./route";
|
|
|
|
function validRequest(overrides: Record<string, unknown> = {}) {
|
|
return new Request("http://localhost/api/velocity/connection", {
|
|
method: "POST",
|
|
headers: { authorization: "Bearer valid-token", "content-type": "application/json" },
|
|
body: JSON.stringify({
|
|
requestId: "8dd9dbdc-020a-4077-983c-77747522de8f",
|
|
serverId: "velocity-main",
|
|
minecraftUuid: "069a79f444e94726a5befca90e38aaf5",
|
|
username: "Notch",
|
|
occurredAt: new Date().toISOString(),
|
|
...overrides,
|
|
}),
|
|
});
|
|
}
|
|
|
|
describe("Velocity connection reporting endpoint", () => {
|
|
beforeEach(() => {
|
|
databaseState.account = { id: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", userId: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" };
|
|
databaseState.inserts = [];
|
|
databaseState.credentialHash = hashToken("valid-token");
|
|
databaseState.replay = false;
|
|
});
|
|
|
|
it("rejects methods other than POST with Problem Details", async () => {
|
|
const response = GET(new Request("http://localhost/api/velocity/connection"));
|
|
expect(response.status).toBe(405);
|
|
expect(response.headers.get("content-type")).toContain("application/problem+json");
|
|
expect(response.headers.get("allow")).toBe("POST");
|
|
});
|
|
|
|
it("requires a server credential", async () => {
|
|
const response = await POST(new Request("http://localhost/api/velocity/connection", {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: "{}",
|
|
}));
|
|
expect(response.status).toBe(401);
|
|
});
|
|
|
|
it("validates the report before database access", async () => {
|
|
const response = await POST(new Request("http://localhost/api/velocity/connection", {
|
|
method: "POST",
|
|
headers: { authorization: "Bearer test", "content-type": "application/json" },
|
|
body: JSON.stringify({ username: "bad name" }),
|
|
}));
|
|
expect(response.status).toBe(400);
|
|
await expect(response.json()).resolves.toMatchObject({ type: "urn:error:invalid-velocity-connection-request", status: 400 });
|
|
});
|
|
|
|
it("rejects invalid or revoked server credentials", async () => {
|
|
databaseState.credentialHash = null;
|
|
const response = await POST(validRequest());
|
|
expect(response.status).toBe(401);
|
|
expect(databaseState.inserts).toHaveLength(0);
|
|
});
|
|
|
|
it("rejects stale reports before recording them", async () => {
|
|
const response = await POST(validRequest({ occurredAt: "2026-01-01T00:00:00.000Z" }));
|
|
expect(response.status).toBe(401);
|
|
expect(databaseState.inserts).toHaveLength(0);
|
|
});
|
|
|
|
it("authenticates and atomically records a confirmed account connection", async () => {
|
|
const response = await POST(validRequest());
|
|
expect(response.status).toBe(204);
|
|
expect(databaseState.inserts).toEqual(expect.arrayContaining([
|
|
expect.objectContaining({ requestId: "8dd9dbdc-020a-4077-983c-77747522de8f", serverId: "velocity-main" }),
|
|
expect.objectContaining({
|
|
type: "games.minecraft.account-manager.game.player.connected",
|
|
subject: "minecraft-account/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
|
|
actorUserId: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
|
|
}),
|
|
]));
|
|
});
|
|
|
|
it("rejects replayed request IDs", async () => {
|
|
databaseState.replay = true;
|
|
const response = await POST(validRequest());
|
|
expect(response.status).toBe(409);
|
|
await expect(response.json()).resolves.toMatchObject({
|
|
type: "urn:error:replayed-velocity-connection-request",
|
|
status: 409,
|
|
});
|
|
});
|
|
|
|
it("does not record an event for an unknown account", async () => {
|
|
databaseState.account = null;
|
|
const response = await POST(validRequest());
|
|
expect(response.status).toBe(404);
|
|
expect(databaseState.inserts).toHaveLength(1);
|
|
});
|
|
});
|