Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 7 additions & 23 deletions server/worldmonitor/infrastructure/v1/list-temporal-anomalies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import type {
TemporalAnomaly as TemporalAnomalyProto,
} from '../../../../src/generated/server/worldmonitor/infrastructure/v1/service_server';

import { getCachedJson, readCachedJson, setCachedJson } from '../../../_shared/redis';
import {
getCachedJson,
readCachedJson,
setCachedJson,
setCachedJsonIfAbsent,
} from '../../../_shared/redis';
import { resolveFireDetectionTotalCount } from '../../../../src/services/wildfires/payload';
import {
BASELINE_TTL,
Expand Down Expand Up @@ -54,29 +59,8 @@ function formatMessage(type: string, count: number, mean: number, multiplier: nu
return `${TYPE_LABELS[type] || type} ${mult} normal for ${WEEKDAY_NAMES[weekday]} (${MONTH_NAMES[month]}) — ${count} vs baseline ${Math.round(mean)}`;
}

function redisCmd(cmd: string[]): { url: string; token: string; body: string } | null {
const url = process.env.UPSTASH_REDIS_REST_URL;
const token = process.env.UPSTASH_REDIS_REST_TOKEN;
if (!url || !token) return null;
return { url, token, body: JSON.stringify(cmd) };
}

async function tryAcquireLock(): Promise<boolean> {
const r = redisCmd(['SET', BASELINE_LOCK_KEY, '1', 'NX', 'EX', String(BASELINE_LOCK_TTL)]);
if (!r) return false;
try {
const resp = await fetch(r.url, {
method: 'POST',
headers: { Authorization: `Bearer ${r.token}`, 'Content-Type': 'application/json' },
body: r.body,
signal: AbortSignal.timeout(3_000),
});
if (!resp.ok) return false;
const data = (await resp.json()) as { result?: string | null };
return data.result === 'OK';
} catch {
return false;
}
return setCachedJsonIfAbsent(BASELINE_LOCK_KEY, 1, BASELINE_LOCK_TTL);
}

/**
Expand Down
40 changes: 39 additions & 1 deletion tests/temporal-anomalies-cache.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
temporalAnomaliesContentMeta,
temporalAnomaliesReadableContentMeta,
} from '../server/worldmonitor/infrastructure/v1/_shared.ts';
import { __resetKeyPrefixCacheForTests } from '../server/_shared/redis.ts';
import { listTemporalAnomalies } from '../server/worldmonitor/infrastructure/v1/list-temporal-anomalies.ts';

/**
Expand All @@ -29,15 +30,28 @@ async function runWithRedisStub(
lockGranted = true,
failedPostKeys = [],
failedGetKeys = [],
}: { lockGranted?: boolean; failedPostKeys?: string[]; failedGetKeys?: string[] } = {},
vercelEnv,
vercelSha,
}: {
lockGranted?: boolean;
failedPostKeys?: string[];
failedGetKeys?: string[];
vercelEnv?: string;
vercelSha?: string;
} = {},
) {
const originalFetch = globalThis.fetch;
const originalUrl = process.env.UPSTASH_REDIS_REST_URL;
const originalToken = process.env.UPSTASH_REDIS_REST_TOKEN;
const originalVercelEnv = process.env.VERCEL_ENV;
const originalVercelSha = process.env.VERCEL_GIT_COMMIT_SHA;
const calls: { method: string; key: string; recordCount?: number; value?: unknown }[] = [];

process.env.UPSTASH_REDIS_REST_URL = 'https://redis.test';
process.env.UPSTASH_REDIS_REST_TOKEN = 'test-token';
if (vercelEnv !== undefined) process.env.VERCEL_ENV = vercelEnv;
if (vercelSha !== undefined) process.env.VERCEL_GIT_COMMIT_SHA = vercelSha;
__resetKeyPrefixCacheForTests();
globalThis.fetch = (async (input: unknown, init: { method?: string; body?: string } = {}) => {
if (init.method === 'POST') {
// Writes POST to `${url}/` with the command in the BODY (`['SET', key, ...]`),
Expand Down Expand Up @@ -83,6 +97,11 @@ async function runWithRedisStub(
globalThis.fetch = originalFetch;
process.env.UPSTASH_REDIS_REST_URL = originalUrl;
process.env.UPSTASH_REDIS_REST_TOKEN = originalToken;
if (originalVercelEnv === undefined) delete process.env.VERCEL_ENV;
else process.env.VERCEL_ENV = originalVercelEnv;
if (originalVercelSha === undefined) delete process.env.VERCEL_GIT_COMMIT_SHA;
else process.env.VERCEL_GIT_COMMIT_SHA = originalVercelSha;
__resetKeyPrefixCacheForTests();
}
}

Expand Down Expand Up @@ -477,6 +496,25 @@ describe('temporal anomalies cache freshness', () => {
);
});

it('uses the preview namespace for the rebuild lock', async () => {
const prefix = 'preview:deadbeef:';
const stale = freshSnapshot(TEMPORAL_ANOMALIES_REBUILD_AFTER_MS + 60_000);
const { response, calls } = await runWithRedisStub(
{ [`${prefix}temporal:anomalies:v1`]: stale },
{
lockGranted: false,
vercelEnv: 'preview',
vercelSha: 'deadbeefcafebabe',
},
);

assert.deepEqual(response, stale);
assert.ok(
calls.some((c) => c.method === 'POST' && c.key === `${prefix}baseline:lock`),
'preview rebuilds must not contend on the production lock key',
);
});

it('counts the pre-cap FIRMS total, not the capped canonical array (#5866)', async () => {
const originalFetch = globalThis.fetch;
const originalUrl = process.env.UPSTASH_REDIS_REST_URL;
Expand Down
Loading