75591bb294
Free 10->5 i kod, test, paywall-copy (12 sprak) och docs. Full trappa: free 5 / trial 14d 25 skann / premium 100/200/400.
120 lines
4.3 KiB
TypeScript
120 lines
4.3 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
||
import { PLAN_DEFINITIONS } from "@app/shared-types";
|
||
import {
|
||
canUseAiScan,
|
||
computeEntitlements,
|
||
resolvePlanFromProductId,
|
||
signEntitlementToken,
|
||
trialEndsAt,
|
||
verifyEntitlementToken,
|
||
} from "../src/index.js";
|
||
|
||
const NOW = new Date("2026-08-02T12:00:00Z");
|
||
const SECRET = "test-secret-with-length";
|
||
|
||
describe("entitlements (spec §45–47)", () => {
|
||
it("aktiv prenumeration ger planens förmåner", () => {
|
||
const ent = computeEntitlements(
|
||
{ plan: "family", status: "active", expiresAt: new Date("2026-09-01") },
|
||
null,
|
||
{ aiScansUsedThisMonth: 10 },
|
||
NOW,
|
||
);
|
||
expect(ent.plan).toBe("family");
|
||
expect(ent.maxHouseholdMembers).toBe(6);
|
||
expect(ent.weekPlanning).toBe(true);
|
||
});
|
||
it("grace period behåller åtkomst (spec §44)", () => {
|
||
const ent = computeEntitlements(
|
||
{
|
||
plan: "household",
|
||
status: "in_grace",
|
||
expiresAt: new Date("2026-08-01"),
|
||
gracePeriodExpiresAt: new Date("2026-08-10"),
|
||
},
|
||
null,
|
||
{ aiScansUsedThisMonth: 0 },
|
||
NOW,
|
||
);
|
||
expect(ent.status).toBe("in_grace");
|
||
expect(ent.weekPlanning).toBe(true);
|
||
});
|
||
it("utgången prenumeration utan grace → free", () => {
|
||
const ent = computeEntitlements(
|
||
{ plan: "family", status: "active", expiresAt: new Date("2026-07-01") },
|
||
null,
|
||
{ aiScansUsedThisMonth: 0 },
|
||
NOW,
|
||
);
|
||
expect(ent.plan).toBe("free");
|
||
});
|
||
it("trial ger full tillgång i 14 dagar (spec §46)", () => {
|
||
const start = new Date("2026-08-01T00:00:00Z");
|
||
const ent = computeEntitlements(
|
||
null,
|
||
{ startedAt: start, endsAt: trialEndsAt(start) },
|
||
{ aiScansUsedThisMonth: 3 },
|
||
NOW,
|
||
);
|
||
expect(ent.status).toBe("trial");
|
||
expect(ent.weekPlanning).toBe(true);
|
||
});
|
||
it("free tier: 5 skanningar och användbar grund (spec §46)", () => {
|
||
const ent = computeEntitlements(null, null, { aiScansUsedThisMonth: 4 }, NOW);
|
||
expect(ent.plan).toBe("free");
|
||
expect(ent.aiScansPerMonth).toBe(5);
|
||
expect(canUseAiScan(ent)).toBe(true);
|
||
expect(canUseAiScan({ ...ent, aiScansUsedThisMonth: 5 })).toBe(false);
|
||
});
|
||
it("mappar productId → plan (härlett ur brand-konfig, aldrig hårdkodat)", () => {
|
||
expect(resolvePlanFromProductId(PLAN_DEFINITIONS.family.appleProductId)).toBe("family");
|
||
expect(resolvePlanFromProductId(PLAN_DEFINITIONS.household.googleProductId)).toBe("household");
|
||
expect(resolvePlanFromProductId("okänd")).toBeNull();
|
||
});
|
||
});
|
||
|
||
describe("signerad entitlement-token (spec §44)", () => {
|
||
const ent = computeEntitlements(
|
||
{ plan: "family", status: "active", expiresAt: new Date("2026-09-01") },
|
||
null,
|
||
{ aiScansUsedThisMonth: 0 },
|
||
NOW,
|
||
);
|
||
|
||
it("signerar och verifierar", () => {
|
||
const token = signEntitlementToken("user-1", ent, SECRET, { now: NOW });
|
||
const result = verifyEntitlementToken(token, SECRET, NOW);
|
||
expect(result.valid).toBe(true);
|
||
if (result.valid) {
|
||
expect(result.payload.sub).toBe("user-1");
|
||
expect(result.payload.plan).toBe("family");
|
||
expect(result.withinGrace).toBe(false);
|
||
}
|
||
});
|
||
it("avvisar manipulerad token", () => {
|
||
const token = signEntitlementToken("user-1", ent, SECRET, { now: NOW });
|
||
const tampered = token.slice(0, -4) + "AAAA";
|
||
expect(verifyEntitlementToken(tampered, SECRET, NOW).valid).toBe(false);
|
||
});
|
||
it("avvisar fel secret", () => {
|
||
const token = signEntitlementToken("user-1", ent, SECRET, { now: NOW });
|
||
expect(verifyEntitlementToken(token, "annan-secret-123", NOW).valid).toBe(false);
|
||
});
|
||
it("grace: giltig efter exp men inom graceExp, ogiltig därefter – ALDRIG permanent boolean", () => {
|
||
const token = signEntitlementToken("user-1", ent, SECRET, {
|
||
now: NOW,
|
||
ttlHours: 24,
|
||
graceDays: 7,
|
||
});
|
||
const afterExp = new Date(NOW.getTime() + 3 * 86_400_000);
|
||
const inGrace = verifyEntitlementToken(token, SECRET, afterExp);
|
||
expect(inGrace.valid).toBe(true);
|
||
if (inGrace.valid) expect(inGrace.withinGrace).toBe(true);
|
||
|
||
const afterGrace = new Date(NOW.getTime() + 9 * 86_400_000);
|
||
const expired = verifyEntitlementToken(token, SECRET, afterGrace);
|
||
expect(expired.valid).toBe(false);
|
||
if (!expired.valid) expect(expired.reason).toBe("expired_beyond_grace");
|
||
});
|
||
});
|