Files
OmniRoute/tests/unit/radar-referrals-page-tab.test.ts
Diego Rodrigues de Sa e Souza d86ea99713 feat(radar): referral links — free-credits tab + default provider link (#9697)
* feat(radar): client-side schema + accessor for referral links (D28)

Server already publishes a signed `referrals` section on the Radar feed
({fixed, campaigns}); this adds the client mirror: RadarFeedSchema gains a
`.default()`-backed `referrals` field (old cached feeds without it stay
valid) with https-only url validation, and src/lib/radar/index.ts exposes
getRadarReferrals()/getDefaultReferralFor() (never throw: flag off, no
cache, or a corrupt/old payload all resolve to the empty shape). The
provider-default lookup itself lives in a new DB-free src/lib/radar/
referrals.ts so it stays safe to import from a "use client" component.

* feat(radar): add GET /api/radar/referrals route (D28)

Local-only route mirroring the /api/radar/catalog gate order: RADAR_ENABLED
off => 404 before any auth check (byte-identical flag-off inertia),
unauthenticated => 401, otherwise 200 with {fixed, campaigns, tier} read
straight from the local cache. Never proxies the private feed server.

* feat(dashboard): add "free credits" tab to the Radar page (D28)

Reuses the existing /dashboard/radar page instead of a new route (less
routing/i18n surface): a second tab lists fixed referral links (grouped by
provider, with requiredAction + an external-link button) and temporary
campaigns (with validUntil). When campaigns is empty and the served tier is
community, shows a soft upsell note — never gates the fixed links list,
which stays fully populated on every tier. Adds 10 new radarPage i18n keys
(English fallback) to all 43 locale files to avoid dropping i18n-ui-coverage
below threshold.

* feat(providers): use Radar default referral link on the provider name (D28)

ProviderPageHeader already linked the provider name to providerInfo.website
with a precedent for a monetized link (the Kimi partner-link note); this
lets a Radar default referral override that URL, reusing the exact same
discreet note instead of a new visual treatment.

Loose coupling: resolveProviderHeaderLink() in providerPageUtils.ts is a
pure function with no @/lib/radar or @/lib/db/* import (asserted by the new
test), so the providers dashboard never depends on the DB-touching Radar
module to render. ProviderDetailPageClient (a "use client" component) is
the only place that fetches Radar data, via the local /api/radar/referrals
route (same pattern the Radar page itself uses) and the DB-free
findDefaultReferral() helper. With RADAR_ENABLED off, no cache, or no
default referral for the provider, the header renders byte-identical to
before this feature existed.

* docs(radar): document referral links / free credits (D28)

Adds a "Referral links (free credits)" section covering the referrals feed
shape, the getRadarReferrals()/getDefaultReferralFor() accessors, the new
GET /api/radar/referrals route, the Radar page's "Free credits" tab, and
the loosely-coupled referral link on the provider-name header. Also
corrects the local-routes count (four -> five) now that /api/radar/
referrals exists.

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-07 10:17:46 -03:00

128 lines
4.7 KiB
TypeScript

/**
* tests/unit/radar-referrals-page-tab.test.ts
*
* Structural regression guard for the "Pegue seus créditos grátis" tab (D28)
* added to the existing /dashboard/radar page (no new dashboard route was
* created — per spec, less i18n/routing surface). Verifies:
*
* - The page source wires a `referrals` tab fetching the LOCAL
* /api/radar/referrals route only (never the private feed server).
* - Every `t("...")` key the page references under the new tab exists (with
* a non-empty value) in en.json — a stand-in for a full i18n-ui-coverage
* run without needing to execute that whole gate here.
* - No new dashboard route directory was created for this feature.
*/
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
const PAGE_PATH = path.resolve(
process.cwd(),
"src/app/(dashboard)/dashboard/radar/page.tsx"
);
const PAGE_SRC = fs.readFileSync(PAGE_PATH, "utf-8");
test("radar page: fetches referrals only from the local /api/radar/referrals route", () => {
assert.ok(
PAGE_SRC.includes('fetch("/api/radar/referrals")'),
"page must fetch the local referrals route"
);
// Never a direct call to an external feed-server URL from this component.
assert.ok(
!/https?:\/\/(?!localhost)/.test(PAGE_SRC.replace(/\/\*[\s\S]*?\*\//g, "")),
"page must never fetch an external URL directly (NEVER proxy the private feed server)"
);
});
test("radar page: renders a referrals tab with catalogTab/freeCreditsTab labels", () => {
assert.ok(PAGE_SRC.includes('t("catalogTab")'));
assert.ok(PAGE_SRC.includes('t("freeCreditsTab")'));
assert.ok(PAGE_SRC.includes('activeTab === "referrals"'));
});
test("radar page: campaigns-empty community upsell is soft (never blocks the fixed links list)", () => {
// The upsell only gates the campaigns section, never `referrals.fixed`.
assert.ok(PAGE_SRC.includes("campaignsUpsellCommunity"));
assert.ok(
!/fixed[\s\S]{0,80}tier === "community"/.test(PAGE_SRC),
"fixed links must never be gated on tier client-side (server already gates by artifact)"
);
});
test("no new dashboard route was created for the free-credits feature (spec: reuse /dashboard/radar)", () => {
const dashboardDir = path.resolve(process.cwd(), "src/app/(dashboard)/dashboard");
assert.ok(
!fs.existsSync(path.join(dashboardDir, "referrals")),
"must not create src/app/(dashboard)/dashboard/referrals/"
);
assert.ok(
!fs.existsSync(path.join(dashboardDir, "radar", "referrals")),
"must not create src/app/(dashboard)/dashboard/radar/referrals/ either — same page, new tab"
);
});
test("every t(\"...\") key referenced in the referrals tab section exists (non-empty) in en.json", () => {
const enMessages = JSON.parse(
fs.readFileSync(
path.resolve(process.cwd(), "src/i18n/messages/en.json"),
"utf-8"
)
);
const radarPage = enMessages.radarPage as Record<string, unknown>;
assert.ok(radarPage, "en.json must have a radarPage namespace");
const referencedKeys = [
"catalogTab",
"freeCreditsTab",
"freeCreditsSubtitle",
"fixedLinksEmpty",
"requiredActionLabel",
"claimButton",
"campaignsTitle",
"campaignsEmpty",
"campaignsUpsellCommunity",
"campaignsValidUntil",
];
for (const key of referencedKeys) {
assert.ok(PAGE_SRC.includes(`t("${key}")` ) || PAGE_SRC.includes(`t("${key}",`), `page.tsx must reference t("${key}")`);
assert.equal(typeof radarPage[key], "string", `en.json radarPage.${key} must be a string`);
assert.ok((radarPage[key] as string).length > 0, `en.json radarPage.${key} must be non-empty`);
}
});
test("all 43 locale message files carry every new radarPage key with a non-empty value", () => {
const messagesDir = path.resolve(process.cwd(), "src/i18n/messages");
const files = fs.readdirSync(messagesDir).filter((f) => f.endsWith(".json"));
assert.ok(files.length >= 40, `expected ~43 locale files, found ${files.length}`);
const NEW_KEYS = [
"catalogTab",
"freeCreditsTab",
"freeCreditsSubtitle",
"fixedLinksEmpty",
"requiredActionLabel",
"claimButton",
"campaignsTitle",
"campaignsEmpty",
"campaignsUpsellCommunity",
"campaignsValidUntil",
];
for (const file of files) {
const data = JSON.parse(fs.readFileSync(path.join(messagesDir, file), "utf-8"));
const radarPage = data.radarPage as Record<string, unknown> | undefined;
assert.ok(radarPage, `${file}: missing radarPage namespace`);
for (const key of NEW_KEYS) {
assert.equal(
typeof radarPage![key],
"string",
`${file}: radarPage.${key} must be a non-empty string`
);
assert.ok((radarPage![key] as string).length > 0, `${file}: radarPage.${key} is empty`);
}
}
});