test: add vitest baseline for critical payment and auth guards
This commit is contained in:
22
tests/convex/ai-utils.test.ts
Normal file
22
tests/convex/ai-utils.test.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { assertNodeBelongsToCanvasOrThrow } from "@/convex/ai-utils";
|
||||
|
||||
describe("assertNodeBelongsToCanvasOrThrow", () => {
|
||||
it("accepts matching node/canvas relation", () => {
|
||||
expect(() =>
|
||||
assertNodeBelongsToCanvasOrThrow(
|
||||
{ canvasId: "canvas_a" },
|
||||
"canvas_a",
|
||||
),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it("rejects mismatching node/canvas relation", () => {
|
||||
expect(() =>
|
||||
assertNodeBelongsToCanvasOrThrow(
|
||||
{ canvasId: "canvas_b" },
|
||||
"canvas_a",
|
||||
),
|
||||
).toThrow("Node does not belong to canvas");
|
||||
});
|
||||
});
|
||||
43
tests/convex/batch-validation-utils.test.ts
Normal file
43
tests/convex/batch-validation-utils.test.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { Id } from "@/convex/_generated/dataModel";
|
||||
import { validateBatchNodesForUserOrThrow } from "@/convex/batch-validation-utils";
|
||||
|
||||
describe("validateBatchNodesForUserOrThrow", () => {
|
||||
it("rejects mixed canvas ids in one batch", async () => {
|
||||
const nodeA = {
|
||||
_id: "node_a" as Id<"nodes">,
|
||||
canvasId: "canvas_a" as Id<"canvases">,
|
||||
};
|
||||
const nodeB = {
|
||||
_id: "node_b" as Id<"nodes">,
|
||||
canvasId: "canvas_b" as Id<"canvases">,
|
||||
};
|
||||
|
||||
await expect(
|
||||
validateBatchNodesForUserOrThrow({
|
||||
userId: "user_1",
|
||||
nodeIds: [nodeA._id, nodeB._id],
|
||||
getNodeById: async (nodeId) => {
|
||||
if (nodeId === nodeA._id) return nodeA;
|
||||
if (nodeId === nodeB._id) return nodeB;
|
||||
return null;
|
||||
},
|
||||
getCanvasById: async () => ({ _id: "canvas_a" as Id<"canvases">, ownerId: "user_1" }),
|
||||
}),
|
||||
).rejects.toThrow("All nodes must belong to the same canvas");
|
||||
});
|
||||
|
||||
it("rejects foreign canvas ownership", async () => {
|
||||
const canvasId = "canvas_a" as Id<"canvases">;
|
||||
const node = { _id: "node_a" as Id<"nodes">, canvasId };
|
||||
|
||||
await expect(
|
||||
validateBatchNodesForUserOrThrow({
|
||||
userId: "user_1",
|
||||
nodeIds: [node._id],
|
||||
getNodeById: async () => node,
|
||||
getCanvasById: async () => ({ _id: canvasId, ownerId: "other_user" }),
|
||||
}),
|
||||
).rejects.toThrow("Canvas not found");
|
||||
});
|
||||
});
|
||||
62
tests/convex/polar-utils.test.ts
Normal file
62
tests/convex/polar-utils.test.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
buildSubscriptionCycleIdempotencyKey,
|
||||
buildSubscriptionRevokedIdempotencyKey,
|
||||
buildTopUpPaidIdempotencyKey,
|
||||
registerWebhookEventOnce,
|
||||
type WebhookEventRepo,
|
||||
} from "@/convex/polar-utils";
|
||||
|
||||
describe("polar idempotency helpers", () => {
|
||||
it("builds stable idempotency keys", () => {
|
||||
expect(
|
||||
buildSubscriptionCycleIdempotencyKey({
|
||||
polarSubscriptionId: "sub_123",
|
||||
currentPeriodStart: 100,
|
||||
currentPeriodEnd: 200,
|
||||
}),
|
||||
).toBe("polar:subscription_cycle:sub_123:100:200");
|
||||
|
||||
expect(
|
||||
buildSubscriptionRevokedIdempotencyKey({
|
||||
userId: "user_1",
|
||||
polarSubscriptionId: "sub_123",
|
||||
}),
|
||||
).toBe("polar:subscription_revoked:sub_123");
|
||||
|
||||
expect(
|
||||
buildSubscriptionRevokedIdempotencyKey({
|
||||
userId: "user_1",
|
||||
}),
|
||||
).toBe("polar:subscription_revoked:user:user_1");
|
||||
|
||||
expect(buildTopUpPaidIdempotencyKey("order_77")).toBe("polar:order_paid:order_77");
|
||||
});
|
||||
|
||||
it("dedupes repeated webhook events by provider/key", async () => {
|
||||
const seen = new Set<string>();
|
||||
let inserted = 0;
|
||||
const repo: WebhookEventRepo = {
|
||||
findByProviderAndKey: async ({ provider, idempotencyKey }) => {
|
||||
const key = `${provider}:${idempotencyKey}`;
|
||||
return seen.has(key) ? { _id: "evt_1" } : null;
|
||||
},
|
||||
insert: async ({ provider, idempotencyKey }) => {
|
||||
seen.add(`${provider}:${idempotencyKey}`);
|
||||
inserted += 1;
|
||||
},
|
||||
};
|
||||
|
||||
const args = {
|
||||
provider: "polar" as const,
|
||||
scope: "topup_paid" as const,
|
||||
idempotencyKey: "polar:order_paid:order_1",
|
||||
userId: "user_1",
|
||||
polarOrderId: "order_1",
|
||||
};
|
||||
|
||||
await expect(registerWebhookEventOnce(repo, args, () => 123)).resolves.toBe(true);
|
||||
await expect(registerWebhookEventOnce(repo, args, () => 123)).resolves.toBe(false);
|
||||
expect(inserted).toBe(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user