Initial commit (unpacked platform)

This commit is contained in:
Sven (AAMOS AI)
2026-08-05 19:21:11 +07:00
commit ac5340195a
314 changed files with 57584 additions and 0 deletions
@@ -0,0 +1,119 @@
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 7 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");
});
});