- implemented universal ingress credential rotation (slug + pin) - fixed event extension logic (`GREATEST(expires_at, NOW())`) - added UI formatter logic for natural dates (`formatNaturalExpiry`, `formatNaturalJoinTime`) - updated event cards to bounded 2-row compact cards - consolidated CLI expanding snippets - overhauled WAI-ARIA support for delegation drawers - removed legacy "Dismiss" mock buttons for cleanly styled "OK" buttons - updated tests and ensured pure zero-dependency SSR JSX compatibility Co-authored-by: mrteye <1945243+mrteye@users.noreply.github.com>
596 lines
18 KiB
TypeScript
596 lines
18 KiB
TypeScript
import { assert, assertEquals, assertExists } from "jsr:@std/assert";
|
|
import { stub } from "jsr:@std/testing/mock";
|
|
import { app } from "../main.ts";
|
|
import { sqlWrapper } from "../db.ts";
|
|
import { valkey } from "../valkey.ts";
|
|
import { rateLimitWrapper } from "../ratelimit.ts";
|
|
|
|
rateLimitWrapper.checkRateLimit = () => Promise.resolve(true);
|
|
rateLimitWrapper.isRateLimited = () => Promise.resolve(false);
|
|
|
|
Deno.test("Multi-Claim Event Passes & Join Endpoints", async (t) => {
|
|
await t.step("POST /api/events creates an event pass", async () => {
|
|
const valkeyStub = stub(valkey, "get", (key: any) => {
|
|
if (String(key) === "admin-session") {
|
|
return Promise.resolve(
|
|
JSON.stringify({ uuid: "admin-uuid", username: "tylerg" }),
|
|
);
|
|
}
|
|
return Promise.resolve(null);
|
|
});
|
|
|
|
const originalSql = sqlWrapper.sql;
|
|
sqlWrapper.sql = ((strings: any, ..._values: any[]) => {
|
|
const query = Array.isArray(strings)
|
|
? strings.join("?")
|
|
: String(strings);
|
|
if (query.includes("INSERT INTO event_passes")) {
|
|
return Promise.resolve([{
|
|
id: "event-uuid-1",
|
|
slug: "deno-lab",
|
|
pin_code: "749-123",
|
|
name: "Deno Workshop",
|
|
max_seats: 50,
|
|
seats_claimed: 0,
|
|
lifespan_hours: 3,
|
|
}]);
|
|
}
|
|
return Promise.resolve([]);
|
|
}) as any;
|
|
|
|
try {
|
|
const res = await app.request("/api/events", {
|
|
method: "POST",
|
|
headers: {
|
|
Authorization: "Bearer admin-session",
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
name: "Deno Workshop",
|
|
slug: "deno-lab",
|
|
pinCode: "749-123",
|
|
maxSeats: 50,
|
|
lifespanHours: 3,
|
|
}),
|
|
});
|
|
|
|
assertEquals(res.status, 200);
|
|
const json = await res.json();
|
|
assert(json.success === true);
|
|
assertEquals(json.event.slug, "deno-lab");
|
|
assertEquals(json.event.pin_code, "749-123");
|
|
} finally {
|
|
sqlWrapper.sql = originalSql;
|
|
valkeyStub.restore();
|
|
}
|
|
});
|
|
|
|
await t.step(
|
|
"POST /api/join normalizes PIN without hyphens and mints guest session",
|
|
async () => {
|
|
const originalSql = sqlWrapper.sql;
|
|
let updateCalled = false;
|
|
let selectCalled = false;
|
|
sqlWrapper.sql = ((strings: any, ...values: any[]) => {
|
|
const query = Array.isArray(strings)
|
|
? strings.join("?")
|
|
: String(strings);
|
|
if (query.includes("SELECT * FROM event_passes")) {
|
|
selectCalled = true;
|
|
// Verify pinNormalized parameter value
|
|
assert(values.includes("749123"));
|
|
return Promise.resolve([{
|
|
id: "event-uuid-1",
|
|
slug: "deno-lab",
|
|
pin_code: "749-123",
|
|
name: "Deno Workshop",
|
|
max_seats: 50,
|
|
seats_claimed: 0,
|
|
lifespan_hours: 3,
|
|
app_id: null,
|
|
}]);
|
|
}
|
|
if (query.includes("UPDATE event_passes")) {
|
|
updateCalled = true;
|
|
return Promise.resolve([{
|
|
id: "event-uuid-1",
|
|
slug: "deno-lab",
|
|
pin_code: "749-123",
|
|
name: "Deno Workshop",
|
|
max_seats: 50,
|
|
seats_claimed: 1,
|
|
lifespan_hours: 3,
|
|
app_id: null,
|
|
}]);
|
|
}
|
|
return Promise.resolve([]);
|
|
}) as any;
|
|
|
|
const valkeySetexStub = stub(
|
|
valkey,
|
|
"setex",
|
|
() => Promise.resolve("OK" as any),
|
|
);
|
|
|
|
const { auditWrapper } = await import("../audit.ts");
|
|
let auditCalled = false;
|
|
let auditPayload: any = null;
|
|
const auditStub = stub(
|
|
auditWrapper,
|
|
"auditLog",
|
|
(_userId, action, resource, metadata, _ip) => {
|
|
if (action === "event_seat_claimed") {
|
|
auditCalled = true;
|
|
auditPayload = { resource, metadata };
|
|
}
|
|
},
|
|
);
|
|
|
|
try {
|
|
const res = await app.request("/api/join", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"X-Forwarded-For": "192.168.1.1",
|
|
},
|
|
body: JSON.stringify({ code: "749-123" }),
|
|
});
|
|
|
|
assertEquals(res.status, 200);
|
|
const json = await res.json();
|
|
assert(json.success === true);
|
|
assert(json.token.startsWith("ay_sess_"));
|
|
assertEquals(json.username, "guest_deno-lab_1");
|
|
|
|
const cookies = res.headers.get("set-cookie");
|
|
assertExists(cookies);
|
|
assert(cookies.includes(`session_id=${json.token};`));
|
|
|
|
assert(selectCalled);
|
|
assert(updateCalled);
|
|
assert(auditCalled);
|
|
assertEquals(auditPayload.resource, "event-uuid-1");
|
|
assertEquals(auditPayload.metadata.slug, "deno-lab");
|
|
assertEquals(auditPayload.metadata.method, "web");
|
|
} finally {
|
|
sqlWrapper.sql = originalSql;
|
|
valkeySetexStub.restore();
|
|
auditStub.restore();
|
|
}
|
|
},
|
|
);
|
|
|
|
await t.step(
|
|
"POST /api/join idempotent re-entry returns existing session without burning a seat",
|
|
async () => {
|
|
const valkeyGetStub = stub(valkey, "get", (key: any) => {
|
|
if (String(key) === "existing-guest-session") {
|
|
return Promise.resolve(
|
|
JSON.stringify({
|
|
uuid: "guest-uuid",
|
|
username: "guest_deno-lab_1",
|
|
sessionId: "existing-guest-session",
|
|
}),
|
|
);
|
|
}
|
|
return Promise.resolve(null);
|
|
});
|
|
|
|
const originalSql = sqlWrapper.sql;
|
|
let updateCalled = false;
|
|
sqlWrapper.sql = ((strings: any, ..._values: any[]) => {
|
|
const query = Array.isArray(strings)
|
|
? strings.join("?")
|
|
: String(strings);
|
|
if (query.includes("SELECT * FROM event_passes")) {
|
|
return Promise.resolve([{
|
|
id: "event-uuid-1",
|
|
slug: "deno-lab",
|
|
pin_code: "749-123",
|
|
name: "Deno Workshop",
|
|
max_seats: 50,
|
|
seats_claimed: 1,
|
|
lifespan_hours: 3,
|
|
app_id: null,
|
|
}]);
|
|
}
|
|
if (query.includes("UPDATE event_passes")) {
|
|
updateCalled = true;
|
|
return Promise.resolve([]);
|
|
}
|
|
return Promise.resolve([]);
|
|
}) as any;
|
|
|
|
const valkeyExpireStub = stub(
|
|
valkey,
|
|
"expire",
|
|
() => Promise.resolve(1 as any),
|
|
);
|
|
|
|
try {
|
|
const res = await app.request("/api/join", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"X-Forwarded-For": "192.168.1.1",
|
|
"Cookie": "session_id=existing-guest-session",
|
|
},
|
|
body: JSON.stringify({ code: "deno-lab" }),
|
|
});
|
|
|
|
assertEquals(res.status, 200);
|
|
const json = await res.json();
|
|
assert(json.success === true);
|
|
assert(json.reused === true);
|
|
assertEquals(json.sessionId, "existing-guest-session");
|
|
assert(!updateCalled); // verify seat was not burned
|
|
} finally {
|
|
sqlWrapper.sql = originalSql;
|
|
valkeyGetStub.restore();
|
|
valkeyExpireStub.restore();
|
|
}
|
|
},
|
|
);
|
|
|
|
await t.step(
|
|
"POST /api/events/:id/rotate-pin rotates both pin and slug",
|
|
async () => {
|
|
const valkeyGetStub = stub(valkey, "get", (key: any) => {
|
|
if (String(key) === "admin-session") {
|
|
return Promise.resolve(
|
|
JSON.stringify({ uuid: "admin-uuid", username: "tylerg" }),
|
|
);
|
|
}
|
|
return Promise.resolve(null);
|
|
});
|
|
|
|
const originalSql = sqlWrapper.sql;
|
|
let updateEventCalled = false;
|
|
let newSlug = "";
|
|
|
|
sqlWrapper.sql = ((strings: any, ..._values: any[]) => {
|
|
const query = Array.isArray(strings)
|
|
? strings.join("?")
|
|
: String(strings);
|
|
if (query.includes("SELECT slug, name FROM event_passes")) {
|
|
return Promise.resolve([{ slug: "deno-lab-1a2b", name: "Deno Lab" }]);
|
|
}
|
|
if (query.includes("UPDATE event_passes") && query.includes("slug =")) {
|
|
updateEventCalled = true;
|
|
newSlug = _values[1]; // second bound parameter is the slug
|
|
return Promise.resolve([{ pin_code: _values[0], slug: newSlug }]);
|
|
}
|
|
return Promise.resolve([]);
|
|
}) as any;
|
|
|
|
try {
|
|
const res = await app.request("/api/events/evt-123/rotate-pin", {
|
|
method: "POST",
|
|
headers: {
|
|
Authorization: "Bearer admin-session",
|
|
},
|
|
});
|
|
|
|
assertEquals(res.status, 200);
|
|
const json = await res.json();
|
|
assert(json.success === true);
|
|
assert(updateEventCalled);
|
|
assert(json.slug.startsWith("deno-lab-"));
|
|
assert(json.slug !== "deno-lab-1a2b");
|
|
assertEquals(json.slug, newSlug);
|
|
} finally {
|
|
sqlWrapper.sql = originalSql;
|
|
valkeyGetStub.restore();
|
|
}
|
|
},
|
|
);
|
|
|
|
await t.step(
|
|
"POST /api/join enforces 5 failed attempts rate limit per IP",
|
|
async () => {
|
|
let isRateLimitedCalled = false;
|
|
let checkRateLimitCalled = false;
|
|
|
|
rateLimitWrapper.isRateLimited = (
|
|
key: string,
|
|
limit: number,
|
|
_window: number,
|
|
) => {
|
|
if (key === "ratelimit:join:fail:192.168.1.99" && limit === 5) {
|
|
isRateLimitedCalled = true;
|
|
return Promise.resolve(true);
|
|
}
|
|
return Promise.resolve(false);
|
|
};
|
|
|
|
rateLimitWrapper.checkRateLimit = () => {
|
|
checkRateLimitCalled = true;
|
|
return Promise.resolve(false);
|
|
};
|
|
|
|
try {
|
|
const res = await app.request("/api/join", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"X-Forwarded-For": "192.168.1.99",
|
|
},
|
|
body: JSON.stringify({ code: "invalid-code" }),
|
|
});
|
|
|
|
assertEquals(res.status, 429);
|
|
const json = await res.json();
|
|
assertEquals(json.error, "Too Many Requests");
|
|
assert(isRateLimitedCalled);
|
|
assert(!checkRateLimitCalled); // didn't reach failure branch
|
|
} finally {
|
|
rateLimitWrapper.isRateLimited = () => Promise.resolve(false);
|
|
rateLimitWrapper.checkRateLimit = () => Promise.resolve(true);
|
|
}
|
|
},
|
|
);
|
|
|
|
await t.step(
|
|
"POST /api/join normalizes slug case-insensitively while preserving hyphens",
|
|
async () => {
|
|
const originalSql = sqlWrapper.sql;
|
|
sqlWrapper.sql = ((strings: any, ...values: any[]) => {
|
|
const query = Array.isArray(strings)
|
|
? strings.join("?")
|
|
: String(strings);
|
|
if (query.includes("SELECT * FROM event_passes")) {
|
|
assert(values.includes("deno-lab-workshop"));
|
|
return Promise.resolve([{
|
|
id: "event-uuid-2",
|
|
slug: "deno-lab-workshop",
|
|
pin_code: "111-222",
|
|
name: "Deno Workshop 2",
|
|
max_seats: 50,
|
|
seats_claimed: 0,
|
|
lifespan_hours: 3,
|
|
app_id: null,
|
|
}]);
|
|
}
|
|
if (query.includes("UPDATE event_passes")) {
|
|
return Promise.resolve([{
|
|
id: "event-uuid-2",
|
|
slug: "deno-lab-workshop",
|
|
pin_code: "111-222",
|
|
name: "Deno Workshop 2",
|
|
max_seats: 50,
|
|
seats_claimed: 1,
|
|
lifespan_hours: 3,
|
|
app_id: null,
|
|
}]);
|
|
}
|
|
return Promise.resolve([]);
|
|
}) as any;
|
|
|
|
const valkeySetexStub = stub(
|
|
valkey,
|
|
"setex",
|
|
() => Promise.resolve("OK" as any),
|
|
);
|
|
|
|
try {
|
|
const res = await app.request("/api/join", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"X-Forwarded-For": "192.168.1.1",
|
|
},
|
|
body: JSON.stringify({ code: " Deno-Lab-Workshop " }),
|
|
});
|
|
|
|
assertEquals(res.status, 200);
|
|
const json = await res.json();
|
|
assert(json.success === true);
|
|
assertEquals(json.username, "guest_deno-lab-workshop_1");
|
|
} finally {
|
|
sqlWrapper.sql = originalSql;
|
|
valkeySetexStub.restore();
|
|
}
|
|
},
|
|
);
|
|
|
|
await t.step(
|
|
"GET /join/:slug?format=env returns CLI export string",
|
|
async () => {
|
|
const originalSql = sqlWrapper.sql;
|
|
sqlWrapper.sql = ((strings: any, ..._values: any[]) => {
|
|
const query = Array.isArray(strings)
|
|
? strings.join("?")
|
|
: String(strings);
|
|
if (query.includes("UPDATE event_passes")) {
|
|
return Promise.resolve([{
|
|
id: "event-uuid-1",
|
|
slug: "deno-lab",
|
|
pin_code: "749-123",
|
|
name: "Deno Workshop",
|
|
max_seats: 50,
|
|
seats_claimed: 2,
|
|
lifespan_hours: 3,
|
|
app_id: null,
|
|
}]);
|
|
}
|
|
return Promise.resolve([]);
|
|
}) as any;
|
|
|
|
const valkeySetexStub = stub(
|
|
valkey,
|
|
"setex",
|
|
() => Promise.resolve("OK" as any),
|
|
);
|
|
|
|
const { auditWrapper } = await import("../audit.ts");
|
|
let auditCalled = false;
|
|
let auditPayload: any = null;
|
|
const auditStub = stub(
|
|
auditWrapper,
|
|
"auditLog",
|
|
(_userId, action, resource, metadata, _ip) => {
|
|
if (action === "event_seat_claimed") {
|
|
auditCalled = true;
|
|
auditPayload = { resource, metadata };
|
|
}
|
|
},
|
|
);
|
|
|
|
try {
|
|
const res = await app.request("/join/deno-lab?format=env", {
|
|
method: "GET",
|
|
headers: { "X-Forwarded-For": "192.168.1.2" },
|
|
});
|
|
|
|
assertEquals(res.status, 200);
|
|
const text = await res.text();
|
|
assert(text.includes('export AUTH_YES_TOKEN="ay_sess_'));
|
|
assert(text.includes('export AUTH_YES_USER="guest_deno-lab_2"'));
|
|
|
|
assert(auditCalled);
|
|
assertEquals(auditPayload.resource, "event-uuid-1");
|
|
assertEquals(auditPayload.metadata.slug, "deno-lab");
|
|
assertEquals(auditPayload.metadata.method, "cli");
|
|
} finally {
|
|
sqlWrapper.sql = originalSql;
|
|
valkeySetexStub.restore();
|
|
auditStub.restore();
|
|
}
|
|
},
|
|
);
|
|
|
|
await t.step(
|
|
"POST /api/events/:id/end master kill switch revokes sessions",
|
|
async () => {
|
|
const valkeyGetStub = stub(valkey, "get", (key: any) => {
|
|
if (String(key) === "admin-session") {
|
|
return Promise.resolve(
|
|
JSON.stringify({ uuid: "admin-uuid", username: "tylerg" }),
|
|
);
|
|
}
|
|
return Promise.resolve(null);
|
|
});
|
|
|
|
const originalSql = sqlWrapper.sql;
|
|
let updateEventCalled = false;
|
|
let deleteSessionsCalled = false;
|
|
|
|
sqlWrapper.sql = ((strings: any, ..._values: any[]) => {
|
|
const query = Array.isArray(strings)
|
|
? strings.join("?")
|
|
: String(strings);
|
|
if (
|
|
query.includes("UPDATE event_passes") &&
|
|
query.includes("is_active = FALSE")
|
|
) {
|
|
updateEventCalled = true;
|
|
return Promise.resolve([{ slug: "deno-lab" }]);
|
|
}
|
|
if (query.includes("DELETE FROM sessions")) {
|
|
deleteSessionsCalled = true;
|
|
return Promise.resolve([{ id: "sess-1" }, { id: "sess-2" }]);
|
|
}
|
|
return Promise.resolve([]);
|
|
}) as any;
|
|
|
|
const valkeyDelStub = stub(
|
|
valkey,
|
|
"del",
|
|
() => Promise.resolve(2 as any),
|
|
);
|
|
|
|
try {
|
|
const res = await app.request("/api/events/evt-123/end", {
|
|
method: "POST",
|
|
headers: {
|
|
Authorization: "Bearer admin-session",
|
|
},
|
|
});
|
|
|
|
assertEquals(res.status, 200);
|
|
const json = await res.json();
|
|
assert(json.success === true);
|
|
assertEquals(json.revokedCount, 2);
|
|
assert(updateEventCalled);
|
|
assert(deleteSessionsCalled);
|
|
} finally {
|
|
sqlWrapper.sql = originalSql;
|
|
valkeyGetStub.restore();
|
|
valkeyDelStub.restore();
|
|
}
|
|
},
|
|
);
|
|
|
|
await t.step(
|
|
"POST /api/events/:id/extend adds time to event and sessions",
|
|
async () => {
|
|
const valkeyGetStub = stub(valkey, "get", (key: any) => {
|
|
if (String(key) === "admin-session") {
|
|
return Promise.resolve(
|
|
JSON.stringify({ uuid: "admin-uuid", username: "tylerg" }),
|
|
);
|
|
}
|
|
return Promise.resolve(null);
|
|
});
|
|
|
|
const futureDate = new Date(Date.now() + 7200000).toISOString();
|
|
const originalSql = sqlWrapper.sql;
|
|
let updateEventCalled = false;
|
|
let updateSessionsCalled = false;
|
|
|
|
sqlWrapper.sql = ((strings: any, ..._values: any[]) => {
|
|
const query = Array.isArray(strings)
|
|
? strings.join("?")
|
|
: String(strings);
|
|
if (
|
|
query.includes("UPDATE event_passes") &&
|
|
query.includes("expires_at = GREATEST(expires_at, NOW()) + interval")
|
|
) {
|
|
updateEventCalled = true;
|
|
return Promise.resolve([{
|
|
slug: "deno-lab",
|
|
expires_at: futureDate,
|
|
}]);
|
|
}
|
|
if (
|
|
query.includes("UPDATE sessions") &&
|
|
query.includes("expires_at = expires_at + interval")
|
|
) {
|
|
updateSessionsCalled = true;
|
|
return Promise.resolve([{ id: "sess-1" }, { id: "sess-2" }]);
|
|
}
|
|
return Promise.resolve([]);
|
|
}) as any;
|
|
|
|
let expireCallCount = 0;
|
|
const valkeyExpireStub = stub(valkey, "expire", () => {
|
|
expireCallCount++;
|
|
return Promise.resolve(1 as any);
|
|
});
|
|
|
|
try {
|
|
const res = await app.request("/api/events/evt-123/extend", {
|
|
method: "POST",
|
|
headers: {
|
|
Authorization: "Bearer admin-session",
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({ extendHours: 2 }),
|
|
});
|
|
|
|
assertEquals(res.status, 200);
|
|
const json = await res.json();
|
|
assert(json.success === true);
|
|
assertEquals(json.expiresAt, futureDate);
|
|
assert(updateEventCalled);
|
|
assert(updateSessionsCalled);
|
|
assertEquals(expireCallCount, 2);
|
|
} finally {
|
|
sqlWrapper.sql = originalSql;
|
|
valkeyGetStub.restore();
|
|
valkeyExpireStub.restore();
|
|
}
|
|
},
|
|
);
|
|
});
|