feat(radar): sync signed supporter offers

This commit is contained in:
diegosouzapw
2026-08-09 02:22:57 -03:00
parent ad1a8460c9
commit c8b410c5f0
14 changed files with 1097 additions and 1 deletions

View File

@@ -0,0 +1,41 @@
/** GET the verified local Radar offers cache. Never proxies the private service. */
import { NextResponse } from "next/server";
import { buildErrorBody, sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";
import { getRadarOffers } from "@/lib/radar";
import { isAuthenticated } from "@/shared/utils/apiAuth";
import { CORS_HEADERS, handleCorsOptions } from "@/shared/utils/cors";
import { isFeatureFlagEnabled } from "@/shared/utils/featureFlags";
export const dynamic = "force-dynamic";
export const revalidate = 0;
export async function OPTIONS() {
return handleCorsOptions();
}
export async function GET(request: Request) {
if (!isFeatureFlagEnabled("RADAR_ENABLED")) {
return NextResponse.json(buildErrorBody(404, "Not found"), {
status: 404,
headers: CORS_HEADERS,
});
}
if (!(await isAuthenticated(request))) {
return NextResponse.json(buildErrorBody(401, "Unauthorized"), {
status: 401,
headers: CORS_HEADERS,
});
}
try {
return NextResponse.json(getRadarOffers(), {
headers: { ...CORS_HEADERS, "Cache-Control": "no-store" },
});
} catch (error: unknown) {
return NextResponse.json(
buildErrorBody(500, sanitizeErrorMessage(error) || "Failed to load Radar offers"),
{ status: 500, headers: CORS_HEADERS }
);
}
}

View File

@@ -0,0 +1,55 @@
/** POST a server-side Radar offers sync. The browser never receives the supporter key. */
import { NextResponse } from "next/server";
import { z } from "zod";
import { buildErrorBody, sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";
import { syncRadarOffers } from "@/lib/radar/offersSync";
import { isAuthenticated } from "@/shared/utils/apiAuth";
import { CORS_HEADERS, handleCorsOptions } from "@/shared/utils/cors";
import { isFeatureFlagEnabled } from "@/shared/utils/featureFlags";
export const dynamic = "force-dynamic";
export const revalidate = 0;
const SyncBodySchema = z.object({}).strict().optional();
export async function OPTIONS() {
return handleCorsOptions();
}
export async function POST(request: Request) {
if (!isFeatureFlagEnabled("RADAR_ENABLED")) {
return NextResponse.json(buildErrorBody(404, "Not found"), {
status: 404,
headers: CORS_HEADERS,
});
}
if (!(await isAuthenticated(request))) {
return NextResponse.json(buildErrorBody(401, "Unauthorized"), {
status: 401,
headers: CORS_HEADERS,
});
}
let body: unknown;
try {
body = await request.json();
} catch {
body = undefined;
}
if (!SyncBodySchema.safeParse(body).success) {
return NextResponse.json(buildErrorBody(400, "Invalid request body"), {
status: 400,
headers: CORS_HEADERS,
});
}
try {
return NextResponse.json(await syncRadarOffers(), { headers: CORS_HEADERS });
} catch (error: unknown) {
return NextResponse.json(
buildErrorBody(500, sanitizeErrorMessage(error) || "Radar offers sync failed"),
{ status: 500, headers: CORS_HEADERS }
);
}
}

View File

@@ -0,0 +1,11 @@
-- 144_radar_offers_cache.sql
-- Single-row cache for the separately signed, live-only Radar offers feed.
CREATE TABLE IF NOT EXISTS radar_offers_cache (
id INTEGER PRIMARY KEY CHECK (id = 1),
version TEXT NOT NULL,
tier TEXT NOT NULL CHECK (tier = 'live'),
payload TEXT NOT NULL,
signature TEXT NOT NULL,
fetched_at TEXT NOT NULL
);

View File

@@ -17,6 +17,9 @@
* - radar_local_model_state: operator-owned display/enabled overrides and
* deletion tombstones, keyed by provider + model ID.
*
* Tables (migration 144):
* - radar_offers_cache: single-row signed live offers feed cache.
*
* The supporter key is encrypted at rest with AES-256-GCM using the same
* `encrypt()`/`decrypt()` helpers from `./encryption.ts` that protect
* provider connection credentials.
@@ -51,6 +54,14 @@ export interface RadarReferralsCache {
fetchedAt: string;
}
export interface RadarOffersCache {
version: string;
tier: "live";
payload: string;
signature: string;
fetchedAt: string;
}
export interface RadarLocalModelState {
provider: string;
modelId: string;
@@ -168,14 +179,16 @@ export function setRadarKey(key: string | null): void {
);
const clearCatalogCache = db.prepare("DELETE FROM radar_feed_cache WHERE id = 1");
const clearReferralsCache = db.prepare("DELETE FROM radar_referrals_cache WHERE id = 1");
const clearOffersCache = db.prepare("DELETE FROM radar_offers_cache WHERE id = 1");
db.transaction(() => {
updateKey.run(encrypted);
// Both signed feeds are entitlement-sensitive. Clearing their cached
// All signed feeds are entitlement-sensitive. Clearing their cached
// variants forces the next sync/read to resolve the new key server-side
// instead of serving data fetched under the previous entitlement.
clearCatalogCache.run();
clearReferralsCache.run();
clearOffersCache.run();
})();
}
@@ -226,6 +239,43 @@ export function setRadarReferralsCache(entry: {
).run(entry.generatedAt, entry.tier, entry.payload, entry.signature, fetchedAt);
}
// ---------------------------------------------------------------------------
// radar_offers_cache
// ---------------------------------------------------------------------------
export function getRadarOffersCache(): RadarOffersCache | null {
const row = getDbInstance()
.prepare(
"SELECT version, tier, payload, signature, fetched_at AS fetchedAt " +
"FROM radar_offers_cache WHERE id = 1"
)
.get() as RadarOffersCache | undefined;
return row ?? null;
}
export function setRadarOffersCache(entry: {
version: string;
tier: "live";
payload: string;
signature: string;
fetchedAt?: string;
}): void {
const fetchedAt = entry.fetchedAt ?? new Date().toISOString();
getDbInstance()
.prepare(
`INSERT INTO radar_offers_cache (id, version, tier, payload, signature, fetched_at)
VALUES (1, ?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET
version = excluded.version,
tier = excluded.tier,
payload = excluded.payload,
signature = excluded.signature,
fetched_at = excluded.fetched_at`
)
.run(entry.version, entry.tier, entry.payload, entry.signature, fetchedAt);
}
// ---------------------------------------------------------------------------
// radar_local_model_state
// ---------------------------------------------------------------------------

View File

@@ -820,6 +820,8 @@ export {
setRadarKey,
getRadarReferralsCache,
setRadarReferralsCache,
getRadarOffersCache,
setRadarOffersCache,
listRadarLocalModelState,
setRadarLocalModelOverride,
clearRadarLocalModelOverride,
@@ -830,6 +832,7 @@ export type {
RadarCache,
RadarSettings,
RadarReferralsCache,
RadarOffersCache,
RadarLocalModelState,
RadarLocalModelOverridePatch,
RadarLocalMergeState,

View File

@@ -12,12 +12,18 @@
import { FREE_MODEL_BUDGETS } from "@omniroute/open-sse/config/freeModelCatalog";
import { RadarFeedSchema, type RadarFeed, type RadarReferral } from "./feedSchema";
import { RadarReferralsFeedSchema, type RadarReferralsFeed } from "./referralsFeedSchema";
import {
filterActiveRadarOffers,
RadarOffersFeedSchema,
type RadarOffer,
} from "./offersFeedSchema";
import { applyFeed, type MergedEntry, type FeedModel } from "./applyFeed";
import { findDefaultReferral } from "./referrals";
import { isFeatureFlagEnabled } from "@/shared/utils/featureFlags";
import {
getRadarCache,
getRadarLocalMergeState,
getRadarOffersCache,
getRadarReferralsCache,
type RadarLocalMergeState,
} from "@/lib/db/radar";
@@ -209,7 +215,54 @@ export function getDefaultReferralFor(
return findDefaultReferral(fixed, provider);
}
// ---------------------------------------------------------------------------
// getRadarOffers
// ---------------------------------------------------------------------------
export interface RadarOffersResult {
offers: RadarOffer[];
meta: { version: string; tier: "live"; fetchedAt: string } | null;
}
export interface GetRadarOffersDeps {
getFlag?: (key: string) => boolean;
getCache?: () => {
version: string;
tier: string;
payload: string;
fetchedAt: string;
} | null;
now?: () => Date;
}
const EMPTY_OFFERS: RadarOffersResult = { offers: [], meta: null };
/** Return only revalidated, unexpired offers from the local live cache. */
export function getRadarOffers(deps: GetRadarOffersDeps = {}): RadarOffersResult {
const {
getFlag = isFeatureFlagEnabled,
getCache: getCacheFn = getRadarOffersCache,
now = () => new Date(),
} = deps;
if (!getFlag("RADAR_ENABLED")) return EMPTY_OFFERS;
const cache = getCacheFn();
if (!cache || cache.tier !== "live") return EMPTY_OFFERS;
try {
const feed = RadarOffersFeedSchema.parse(JSON.parse(cache.payload));
if (feed.version !== cache.version || feed.tier !== "live") return EMPTY_OFFERS;
return {
offers: filterActiveRadarOffers(feed.offers, now()),
meta: { version: cache.version, tier: "live", fetchedAt: cache.fetchedAt },
};
} catch {
return EMPTY_OFFERS;
}
}
// Re-export merge types for convenience
export { applyFeed, type MergedEntry, type FeedModel } from "./applyFeed";
export { findDefaultReferral } from "./referrals";
export type { RadarReferral } from "./feedSchema";
export type { RadarOffer, RadarOfferBenefit, RadarOfferLocalizedText } from "./offersFeedSchema";

View File

@@ -0,0 +1,147 @@
/**
* Closed client mirror of the private Radar offers feed contract.
*
* Keep this shape byte-compatible with `src/offers/schema.ts` in the private
* server. The canonical fixture in `tests/fixtures/` pins that cross-repo
* contract without embedding any real offer or partner data.
*/
import { z } from "zod";
const OFFER_ID_PATTERN = /^[a-z0-9][a-z0-9._-]{0,119}$/;
const PROVIDER_ID_PATTERN = /^[a-z0-9][a-z0-9._-]{0,119}$/;
export const RadarOfferLocalizedTextSchema = z
.object({
en: z.string().min(1),
pt: z.string().min(1).optional(),
})
.strict();
export type RadarOfferLocalizedText = z.infer<typeof RadarOfferLocalizedTextSchema>;
const HttpsUrlSchema = z
.string()
.url()
.superRefine((value, ctx) => {
const parsed = new URL(value);
if (parsed.protocol !== "https:" || parsed.username || parsed.password) {
ctx.addIssue({ code: "custom", message: "offer URL must be credential-free HTTPS" });
}
});
const PercentBenefitSchema = z
.object({
kind: z.literal("percent_off"),
basisPoints: z.number().int().min(1).max(10_000),
})
.strict();
const CreditBenefitSchema = z
.object({
kind: z.literal("credit"),
amountMinor: z.number().int().positive(),
currency: z.string().regex(/^[A-Z]{3}$/),
})
.strict();
const TrialBenefitSchema = z
.object({
kind: z.literal("trial_days"),
days: z.number().int().min(1).max(3_650),
})
.strict();
export const RadarOfferBenefitSchema = z.discriminatedUnion("kind", [
PercentBenefitSchema,
CreditBenefitSchema,
TrialBenefitSchema,
]);
export type RadarOfferBenefit = z.infer<typeof RadarOfferBenefitSchema>;
function isStrictlyBetter(benefit: RadarOfferBenefit, publicBenefit: RadarOfferBenefit): boolean {
if (benefit.kind !== publicBenefit.kind) return false;
if (benefit.kind === "percent_off" && publicBenefit.kind === "percent_off") {
return benefit.basisPoints > publicBenefit.basisPoints;
}
if (benefit.kind === "trial_days" && publicBenefit.kind === "trial_days") {
return benefit.days > publicBenefit.days;
}
if (benefit.kind === "credit" && publicBenefit.kind === "credit") {
return (
benefit.currency === publicBenefit.currency && benefit.amountMinor > publicBenefit.amountMinor
);
}
return false;
}
export const RadarOfferSchema = z
.object({
id: z.string().regex(OFFER_ID_PATTERN),
provider: z.string().regex(PROVIDER_ID_PATTERN),
title: RadarOfferLocalizedTextSchema,
description: RadarOfferLocalizedTextSchema,
benefit: RadarOfferBenefitSchema,
publicBenefit: RadarOfferBenefitSchema.nullable(),
conditions: RadarOfferLocalizedTextSchema,
validUntil: z.string().datetime().nullable(),
url: HttpsUrlSchema,
partner: z.boolean(),
})
.strict()
.superRefine((offer, ctx) => {
if (!offer.partner && offer.publicBenefit !== null) {
ctx.addIssue({
code: "custom",
path: ["publicBenefit"],
message: "official offer has no partner baseline",
});
return;
}
if (
offer.partner &&
(offer.publicBenefit === null || !isStrictlyBetter(offer.benefit, offer.publicBenefit))
) {
ctx.addIssue({
code: "custom",
path: ["publicBenefit"],
message: "partner benefit must be strictly better than a comparable public benefit",
});
}
});
export type RadarOffer = z.infer<typeof RadarOfferSchema>;
export const RadarOffersFeedSchema = z
.object({
feed: z.literal("omniroute-radar-offers"),
schemaVersion: z.literal(1),
version: z.string().regex(/^\d{4}\.\d{2}\.\d{2}\.\d+$/),
generatedAt: z.string().datetime(),
tier: z.literal("live"),
count: z.number().int().nonnegative(),
offers: z.array(RadarOfferSchema),
})
.strict()
.superRefine((feed, ctx) => {
if (feed.count !== feed.offers.length) {
ctx.addIssue({ code: "custom", path: ["count"], message: "offer count mismatch" });
}
});
export type RadarOffersFeed = z.infer<typeof RadarOffersFeedSchema>;
export function filterActiveRadarOffers(
offers: readonly RadarOffer[],
now: Date = new Date()
): RadarOffer[] {
const nowMs = now.getTime();
return offers.filter(
(offer) => offer.validUntil === null || Date.parse(offer.validUntil) > nowMs
);
}
export function localizeRadarOfferText(text: RadarOfferLocalizedText, locale: string): string {
return locale.toLowerCase().startsWith("pt") && text.pt ? text.pt : text.en;
}

152
src/lib/radar/offersSync.ts Normal file
View File

@@ -0,0 +1,152 @@
/**
* Server-side sync for the separately signed, supporter-only Radar offers feed.
* Every failure preserves the last verified local cache.
*/
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";
import { isFeatureFlagEnabled } from "@/shared/utils/featureFlags";
import { RadarOffersFeedSchema, type RadarOffersFeed } from "./offersFeedSchema";
import { compareVersions, type RadarSettingsSnapshot } from "./sync";
import { verifyFeedBytes } from "./verify";
const DEFAULT_FEED_BASE_URL = "https://radar.omniroute.online";
const SYNC_TIMEOUT_MS = 30_000;
const MAX_FEED_BYTES = 10 * 1024 * 1024;
export type OffersSyncStatus =
| { status: "disabled" }
| { status: "opt_out" }
| { status: "no_key" }
| { status: "invalid_signature" }
| { status: "invalid_schema" }
| { status: "wrong_tier" }
| { status: "stale" }
| { status: "too_large" }
| { status: "updated"; version: string }
| { status: "error"; reason: string };
export interface RadarOffersCacheEntry {
version: string;
tier: "live";
payload: string;
signature: string;
fetchedAt?: string;
}
export interface OffersSyncDeps {
fetch?: typeof globalThis.fetch;
now?: () => Date;
getFlag?: (key: string) => boolean;
getSettings?: () => RadarSettingsSnapshot;
getCache?: () => RadarOffersCacheEntry | null;
setCache?: (entry: RadarOffersCacheEntry) => void;
}
async function readBoundedBytes(response: Response): Promise<Buffer | null> {
const contentLength = response.headers.get("content-length");
if (contentLength !== null) {
const declared = Number(contentLength);
if (Number.isFinite(declared) && declared > MAX_FEED_BYTES) return null;
}
const body = response.body as ReadableStream<Uint8Array> | null | undefined;
if (!body || typeof body.getReader !== "function") {
const buffered = Buffer.from(await response.arrayBuffer());
return buffered.byteLength > MAX_FEED_BYTES ? null : buffered;
}
const reader = body.getReader();
const chunks: Uint8Array[] = [];
let total = 0;
for (;;) {
const { done, value } = await reader.read();
if (done) break;
if (!value) continue;
total += value.byteLength;
if (total > MAX_FEED_BYTES) {
await reader.cancel().catch(() => undefined);
return null;
}
chunks.push(value);
}
return Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)));
}
export async function syncRadarOffers(deps: OffersSyncDeps = {}): Promise<OffersSyncStatus> {
const {
fetch: fetchFn = globalThis.fetch,
now = () => new Date(),
getFlag = isFeatureFlagEnabled,
getSettings: getSettingsFn,
getCache: getCacheFn,
setCache: setCacheFn,
} = deps;
try {
if (!getFlag("RADAR_ENABLED")) return { status: "disabled" };
const settings = getSettingsFn
? getSettingsFn()
: (await import("@/lib/db/radar")).getRadarSettings();
if (!settings.optIn) return { status: "opt_out" };
if (!settings.supporterKey) return { status: "no_key" };
const baseUrl = (process.env.RADAR_FEED_URL || DEFAULT_FEED_BASE_URL).replace(/\/+$/, "");
const response = await fetchFn(`${baseUrl}/v1/offers/latest`, {
method: "GET",
headers: { Authorization: `Bearer ${settings.supporterKey}` },
signal: AbortSignal.timeout(SYNC_TIMEOUT_MS),
});
if (!response.ok) {
return {
status: "error",
reason: `Offers feed request failed with status ${response.status}`,
};
}
const rawBytes = await readBoundedBytes(response);
if (!rawBytes) return { status: "too_large" };
const signature = response.headers.get("x-omniroute-feed-signature") ?? "";
if (!verifyFeedBytes(rawBytes, signature)) return { status: "invalid_signature" };
let feed: RadarOffersFeed;
try {
feed = RadarOffersFeedSchema.parse(JSON.parse(rawBytes.toString("utf8")));
} catch {
return { status: "invalid_schema" };
}
if (response.headers.get("x-omniroute-feed-tier") !== "live" || feed.tier !== "live") {
return { status: "wrong_tier" };
}
const existing = getCacheFn
? getCacheFn()
: (await import("@/lib/db/radar")).getRadarOffersCache();
if (existing && compareVersions(feed.version, existing.version) <= 0) {
return { status: "stale" };
}
const cacheEntry: RadarOffersCacheEntry = {
version: feed.version,
tier: "live",
payload: rawBytes.toString("utf8"),
signature,
fetchedAt: now().toISOString(),
};
if (setCacheFn) {
setCacheFn(cacheEntry);
} else {
(await import("@/lib/db/radar")).setRadarOffersCache(cacheEntry);
}
return { status: "updated", version: feed.version };
} catch (error: unknown) {
const reason = (sanitizeErrorMessage(error) || "Radar offers sync failed").replace(
/omr_[a-f0-9]{40}/gi,
"[REDACTED]"
);
return { status: "error", reason };
}
}

View File

@@ -0,0 +1,63 @@
{
"feed": "omniroute-radar-offers",
"schemaVersion": 1,
"version": "2026.08.09.1",
"generatedAt": "2026-08-09T12:00:00.000Z",
"tier": "live",
"count": 2,
"offers": [
{
"id": "example-official-trial",
"provider": "example",
"title": {
"en": "Official trial",
"pt": "Teste oficial"
},
"description": {
"en": "Canonical official-offer fixture",
"pt": "Fixture canônico de oferta oficial"
},
"benefit": {
"kind": "trial_days",
"days": 14
},
"publicBenefit": null,
"conditions": {
"en": "Fixture only; not a real offer",
"pt": "Somente fixture; não é uma oferta real"
},
"validUntil": "2099-12-31T23:59:59.000Z",
"url": "https://provider.example/official-trial",
"partner": false
},
{
"id": "example-partner-credit",
"provider": "example",
"title": {
"en": "Partner credit",
"pt": "Crédito de parceiro"
},
"description": {
"en": "Canonical partner-offer fixture",
"pt": "Fixture canônico de oferta de parceiro"
},
"benefit": {
"kind": "credit",
"amountMinor": 1000,
"currency": "USD"
},
"publicBenefit": {
"kind": "credit",
"amountMinor": 500,
"currency": "USD"
},
"conditions": {
"en": "Fixture only; not a real offer",
"pt": "Somente fixture; não é uma oferta real"
},
"validUntil": null,
"url": "https://provider.example/partner-credit",
"partner": true
}
]
}

View File

@@ -0,0 +1,60 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import test from "node:test";
import { getRadarOffers } from "../../src/lib/radar/index.ts";
async function fixturePayload(): Promise<string> {
return readFile(new URL("../fixtures/radar-offers-canonical.json", import.meta.url), "utf8");
}
test("offers accessor short-circuits before cache when Radar is disabled", () => {
let reads = 0;
const result = getRadarOffers({
getFlag: () => false,
getCache: () => {
reads += 1;
throw new Error("cache must not be read");
},
});
assert.deepEqual(result, { offers: [], meta: null });
assert.equal(reads, 0);
});
test("offers accessor fails closed for missing, corrupt, or non-live cache", () => {
for (const cache of [
null,
{ version: "x", tier: "live", payload: "not-json", fetchedAt: "now" },
{ version: "x", tier: "community", payload: "{}", fetchedAt: "now" },
]) {
assert.deepEqual(getRadarOffers({ getFlag: () => true, getCache: () => cache }), {
offers: [],
meta: null,
});
}
});
test("offers accessor revalidates the cache and removes expired entries", async () => {
const payload = await fixturePayload();
const result = getRadarOffers({
getFlag: () => true,
getCache: () => ({
version: "2026.08.09.1",
tier: "live",
payload,
fetchedAt: "2026-08-09T12:05:00.000Z",
}),
now: () => new Date("2100-01-01T00:00:00.000Z"),
});
assert.deepEqual(
result.offers.map(({ id }) => id),
["example-partner-credit"]
);
assert.deepEqual(result.meta, {
version: "2026.08.09.1",
tier: "live",
fetchedAt: "2026-08-09T12:05:00.000Z",
});
});

View File

@@ -0,0 +1,71 @@
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";
import { test } from "node:test";
import {
RadarOfferSchema,
RadarOffersFeedSchema,
filterActiveRadarOffers,
localizeRadarOfferText,
} from "../../src/lib/radar/offersFeedSchema.ts";
const EXPECTED_FIXTURE_HASH = "f01a4c03a72adbffa944b4bcc8610ad2fec31dc500feaed18bdd9d1af4f06216";
async function canonicalFixture(): Promise<Buffer> {
return readFile(new URL("../fixtures/radar-offers-canonical.json", import.meta.url));
}
test("offers contract fixture is byte-identical to the private server contract", async () => {
const bytes = await canonicalFixture();
assert.equal(createHash("sha256").update(bytes).digest("hex"), EXPECTED_FIXTURE_HASH);
const feed = RadarOffersFeedSchema.parse(JSON.parse(bytes.toString("utf8")));
assert.equal(feed.count, 2);
assert.deepEqual(
feed.offers.map(({ id, partner }) => ({ id, partner })),
[
{ id: "example-official-trial", partner: false },
{ id: "example-partner-credit", partner: true },
]
);
});
test("partner offer must be strictly better than a comparable public benefit", async () => {
const bytes = await canonicalFixture();
const partner = RadarOffersFeedSchema.parse(JSON.parse(bytes.toString("utf8"))).offers[1]!;
assert.equal(
RadarOfferSchema.safeParse({
...partner,
benefit: { kind: "credit", amountMinor: 500, currency: "USD" },
}).success,
false
);
assert.equal(
RadarOfferSchema.safeParse({
...partner,
publicBenefit: { kind: "trial_days", days: 30 },
}).success,
false
);
});
test("active projection filters expired offers and localizes with English fallback", async () => {
const bytes = await canonicalFixture();
const feed = RadarOffersFeedSchema.parse(JSON.parse(bytes.toString("utf8")));
const expired = {
...feed.offers[0]!,
id: "expired",
validUntil: "2026-08-01T00:00:00.000Z",
};
assert.deepEqual(
filterActiveRadarOffers([...feed.offers, expired], new Date("2026-08-09T12:00:00.000Z")).map(
({ id }) => id
),
["example-official-trial", "example-partner-credit"]
);
assert.equal(localizeRadarOfferText({ en: "English", pt: "Português" }, "pt-BR"), "Português");
assert.equal(localizeRadarOfferText({ en: "English" }, "de"), "English");
});

View File

@@ -0,0 +1,85 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import test from "node:test";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-radar-offers-db-"));
process.env.DATA_DIR = TEST_DATA_DIR;
process.env.STORAGE_ENCRYPTION_KEY = "test-encryption-key-for-radar-offers-db-32b!";
const core = await import("../../src/lib/db/core.ts");
const radar = await import("../../src/lib/db/radar.ts");
function resetStorage(): void {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
}
test.beforeEach(resetStorage);
test.after(() => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
delete process.env.STORAGE_ENCRYPTION_KEY;
});
test("Radar offers cache migration creates a single-row byte-preserving store", () => {
const db = core.getDbInstance();
assert.equal(radar.getRadarOffersCache(), null);
radar.setRadarOffersCache({
version: "2026.08.09.1",
tier: "live",
payload: '{"byte":"exact"}\n',
signature: "signed",
fetchedAt: "2026-08-09T12:05:00.000Z",
});
radar.setRadarOffersCache({
version: "2026.08.09.2",
tier: "live",
payload: '{"replacement":true}',
signature: "signed-again",
fetchedAt: "2026-08-09T12:10:00.000Z",
});
assert.deepEqual(radar.getRadarOffersCache(), {
version: "2026.08.09.2",
tier: "live",
payload: '{"replacement":true}',
signature: "signed-again",
fetchedAt: "2026-08-09T12:10:00.000Z",
});
const row = db.prepare("SELECT COUNT(*) AS count FROM radar_offers_cache").get() as {
count: number;
};
assert.equal(row.count, 1);
});
test("changing the supporter key atomically invalidates every entitlement-sensitive cache", () => {
const db = core.getDbInstance();
radar.setRadarCache({ version: "2026.08.09.1", tier: "live", payload: "{}", signature: "a" });
radar.setRadarReferralsCache({
generatedAt: "2026-08-09T12:00:00.000Z",
tier: "live",
payload: "{}",
signature: "b",
});
radar.setRadarOffersCache({
version: "2026.08.09.1",
tier: "live",
payload: "{}",
signature: "c",
});
radar.setRadarKey(`omr_${"a".repeat(40)}`);
assert.equal(radar.getRadarCache(), null);
assert.equal(radar.getRadarReferralsCache(), null);
assert.equal(radar.getRadarOffersCache(), null);
const stored = db
.prepare("SELECT supporter_key_encrypted AS key FROM radar_settings WHERE id = 1")
.get() as { key: string };
assert.ok(!stored.key.includes("omr_"), "supporter key must stay encrypted at rest");
});

View File

@@ -0,0 +1,120 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import { SignJWT } from "jose";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-radar-offers-routes-"));
process.env.DATA_DIR = TEST_DATA_DIR;
process.env.STORAGE_ENCRYPTION_KEY = "test-encryption-key-for-radar-offers-routes-32b!";
process.env.JWT_SECRET = "test-jwt-secret-for-radar-offers-routes";
process.env.INITIAL_PASSWORD = "test-bootstrap-password-for-radar-offers-routes";
const core = await import("../../src/lib/db/core.ts");
const radarDb = await import("../../src/lib/db/radar.ts");
async function authHeaders(): Promise<Record<string, string>> {
const token = await new SignJWT({ authenticated: true })
.setProtectedHeader({ alg: "HS256" })
.setIssuedAt()
.setExpirationTime("1h")
.sign(new TextEncoder().encode(process.env.JWT_SECRET));
return { Cookie: `auth_token=${token}` };
}
function resetStorage(): void {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
}
function request(
pathname: string,
method: "GET" | "POST",
headers: Record<string, string> = {},
body?: unknown
) {
return new Request(`http://localhost:20128${pathname}`, {
method,
headers: { ...headers, ...(body === undefined ? {} : { "content-type": "application/json" }) },
body: body === undefined ? undefined : JSON.stringify(body),
});
}
test.after(() => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
delete process.env.RADAR_ENABLED;
delete process.env.STORAGE_ENCRYPTION_KEY;
});
test("offers routes are inert before auth when the feature flag is off", async () => {
resetStorage();
delete process.env.RADAR_ENABLED;
const { GET } = await import("../../src/app/api/radar/offers/route.ts");
const { POST } = await import("../../src/app/api/radar/offers/sync/route.ts");
assert.equal((await GET(request("/api/radar/offers", "GET"))).status, 404);
assert.equal((await POST(request("/api/radar/offers/sync", "POST"))).status, 404);
});
test("offers routes require dashboard or management authentication", async () => {
resetStorage();
process.env.RADAR_ENABLED = "true";
const { GET } = await import("../../src/app/api/radar/offers/route.ts");
const { POST } = await import("../../src/app/api/radar/offers/sync/route.ts");
assert.equal((await GET(request("/api/radar/offers", "GET"))).status, 401);
assert.equal((await POST(request("/api/radar/offers/sync", "POST"))).status, 401);
});
test("GET offers returns only the local cache and never exposes supporter key material", async () => {
resetStorage();
process.env.RADAR_ENABLED = "true";
const payload = fs.readFileSync(
path.resolve(process.cwd(), "tests/fixtures/radar-offers-canonical.json"),
"utf8"
);
radarDb.setRadarOffersCache({
version: "2026.08.09.1",
tier: "live",
payload,
signature: "fixture-signature",
fetchedAt: "2026-08-09T12:05:00.000Z",
});
const { GET } = await import("../../src/app/api/radar/offers/route.ts");
const response = await GET(request("/api/radar/offers", "GET", await authHeaders()));
const body = await response.json();
assert.equal(response.status, 200);
assert.equal(body.offers.length, 2);
assert.equal(body.meta.tier, "live");
assert.ok(!JSON.stringify(body).includes("omr_"));
});
test("POST offers sync validates an empty body and gates a missing key without network", async () => {
resetStorage();
process.env.RADAR_ENABLED = "true";
radarDb.setRadarOptIn(true);
const { POST } = await import("../../src/app/api/radar/offers/sync/route.ts");
const invalid = await POST(
request("/api/radar/offers/sync", "POST", await authHeaders(), { provider: "groq" })
);
assert.equal(invalid.status, 400);
const response = await POST(request("/api/radar/offers/sync", "POST", await authHeaders()));
assert.equal(response.status, 200);
assert.deepEqual(await response.json(), { status: "no_key" });
});
test("local offer routes never call the private server directly", () => {
for (const file of [
"src/app/api/radar/offers/route.ts",
"src/app/api/radar/offers/sync/route.ts",
]) {
const source = fs.readFileSync(path.resolve(process.cwd(), file), "utf8");
assert.ok(!/fetch\(/.test(source), `${file} must stay local-only`);
}
});

View File

@@ -0,0 +1,185 @@
import assert from "node:assert/strict";
import crypto from "node:crypto";
import { readFile } from "node:fs/promises";
import test from "node:test";
const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
process.env.RADAR_FEED_PUBKEY = publicKey
.export({ type: "spki", format: "der" })
.toString("base64");
const offersSync = await import("../../src/lib/radar/offersSync.ts");
async function fixtureFeed(): Promise<Record<string, unknown>> {
const bytes = await readFile(new URL("../fixtures/radar-offers-canonical.json", import.meta.url));
return JSON.parse(bytes.toString("utf8")) as Record<string, unknown>;
}
function sign(bytes: Buffer): string {
return crypto.sign(null, bytes, privateKey).toString("base64");
}
function response(body: Buffer, headers: Record<string, string> = {}, status = 200): Response {
return {
ok: status >= 200 && status < 300,
status,
headers: new Headers(headers),
arrayBuffer: async () => body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength),
} as Response;
}
function liveSettings(supporterKey: string | null = `omr_${"a".repeat(40)}`) {
return { optIn: true, supporterKey };
}
test("offers sync gates flag, opt-in, and missing supporter key before fetch", async () => {
for (const expected of ["disabled", "opt_out", "no_key"] as const) {
let fetched = false;
const result = await offersSync.syncRadarOffers({
getFlag: () => expected !== "disabled",
getSettings: () =>
expected === "opt_out" ? { optIn: false, supporterKey: null } : liveSettings(null),
fetch: (async () => {
fetched = true;
return response(Buffer.from("{}"));
}) as typeof fetch,
});
assert.equal(result.status, expected);
assert.equal(fetched, false);
}
});
test("valid live offer feed sends Bearer server-side and caches exact signed bytes", async () => {
const feed = await fixtureFeed();
const bytes = Buffer.from(JSON.stringify(feed));
const signature = sign(bytes);
const writes: offersSync.RadarOffersCacheEntry[] = [];
let requestUrl = "";
let authorization = "";
const result = await offersSync.syncRadarOffers({
getFlag: () => true,
getSettings: () => liveSettings(),
getCache: () => null,
setCache: (entry) => writes.push(entry),
fetch: (async (input, init) => {
requestUrl = String(input);
authorization = new Headers(init?.headers).get("authorization") ?? "";
return response(bytes, {
"x-omniroute-feed-signature": signature,
"x-omniroute-feed-tier": "live",
});
}) as typeof fetch,
now: () => new Date("2026-08-09T12:05:00.000Z"),
});
assert.deepEqual(result, { status: "updated", version: "2026.08.09.1" });
assert.equal(requestUrl, "https://radar.omniroute.online/v1/offers/latest");
assert.equal(authorization, `Bearer omr_${"a".repeat(40)}`);
assert.equal(writes[0]!.payload, bytes.toString("utf8"));
assert.equal(writes[0]!.signature, signature);
assert.equal(writes[0]!.tier, "live");
});
test("signature, schema, and live-tier failures preserve the last good cache", async () => {
const feed = await fixtureFeed();
const validBytes = Buffer.from(JSON.stringify(feed));
const cases: Array<{ expected: string; bytes: Buffer; signature: string; tier: string | null }> =
[
{ expected: "invalid_signature", bytes: validBytes, signature: "invalid", tier: "live" },
{
expected: "invalid_schema",
bytes: Buffer.from('{"feed":"wrong"}'),
signature: "valid-for-case",
tier: "live",
},
{ expected: "wrong_tier", bytes: validBytes, signature: "valid-for-case", tier: null },
{ expected: "wrong_tier", bytes: validBytes, signature: "valid-for-case", tier: "community" },
];
for (const item of cases) {
item.signature = item.expected === "invalid_signature" ? item.signature : sign(item.bytes);
let written = false;
const result = await offersSync.syncRadarOffers({
getFlag: () => true,
getSettings: () => liveSettings(),
getCache: () => ({
version: "2026.08.08.1",
tier: "live",
payload: "last-good",
signature: "old",
}),
setCache: () => {
written = true;
},
fetch: (async () =>
response(item.bytes, {
"x-omniroute-feed-signature": item.signature,
...(item.tier ? { "x-omniroute-feed-tier": item.tier } : {}),
})) as typeof fetch,
});
assert.equal(result.status, item.expected);
assert.equal(written, false);
}
});
test("same or older signed offer versions are rejected as stale", async () => {
const feed = await fixtureFeed();
const bytes = Buffer.from(JSON.stringify(feed));
let written = false;
const result = await offersSync.syncRadarOffers({
getFlag: () => true,
getSettings: () => liveSettings(),
getCache: () => ({
version: "2026.08.09.1",
tier: "live",
payload: "last-good",
signature: "old",
}),
setCache: () => {
written = true;
},
fetch: (async () =>
response(bytes, {
"x-omniroute-feed-signature": sign(bytes),
"x-omniroute-feed-tier": "live",
})) as typeof fetch,
});
assert.equal(result.status, "stale");
assert.equal(written, false);
});
test("oversized and sanitized network failures never overwrite the cache or leak the key", async () => {
let written = false;
const tooLarge = await offersSync.syncRadarOffers({
getFlag: () => true,
getSettings: () => liveSettings(),
getCache: () => null,
setCache: () => {
written = true;
},
fetch: (async () =>
response(Buffer.from("ignored"), {
"content-length": String(10 * 1024 * 1024 + 1),
})) as typeof fetch,
});
assert.equal(tooLarge.status, "too_large");
const secret = `omr_${"b".repeat(40)}`;
const failed = await offersSync.syncRadarOffers({
getFlag: () => true,
getSettings: () => liveSettings(secret),
getCache: () => null,
setCache: () => {
written = true;
},
fetch: (async () => {
throw new Error(`upstream failed for ${secret}\n at /private/path.ts:1:1`);
}) as typeof fetch,
});
assert.equal(failed.status, "error");
assert.ok(!("reason" in failed) || !failed.reason.includes(secret));
assert.ok(!("reason" in failed) || !failed.reason.includes("/private/path"));
assert.equal(written, false);
});