Files
Cibello-app/packages/subscriptions/test/subscriptions.test.ts
T
Claude 88876a24b7 fix(subscriptions): trial 14 dagar konsekvent i UI/docs/legal/butik
TRIAL_DAYS var redan 14 i koden men auth.trialNote (12 sprak), butiksmetadata, juridik-utkast, API-ref, exec-summary och UX-doc sa 7. Nu 14 overallt. Rorde EJ icke-trial 7-dagar (inbjudningslank, backup, S3-lifecycle, grace, retention). Beslut: Johan (battre onboarding).
2026-08-17 17:04:00 +00:00

120 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 §4547)", () => {
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: 10 skanningar och användbar grund (spec §46)", () => {
const ent = computeEntitlements(null, null, { aiScansUsedThisMonth: 9 }, NOW);
expect(ent.plan).toBe("free");
expect(ent.aiScansPerMonth).toBe(10);
expect(canUseAiScan(ent)).toBe(true);
expect(canUseAiScan({ ...ent, aiScansUsedThisMonth: 10 })).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");
});
});