diff --git a/src/app/(dashboard)/dashboard/settings/components/SystemStorageTab.tsx b/src/app/(dashboard)/dashboard/settings/components/SystemStorageTab.tsx
index 7f4b57aeac..e7a382026d 100644
--- a/src/app/(dashboard)/dashboard/settings/components/SystemStorageTab.tsx
+++ b/src/app/(dashboard)/dashboard/settings/components/SystemStorageTab.tsx
@@ -1541,7 +1541,7 @@ export default function SystemStorageTab() {
{t("resetUsageDataDesc") ||
- "Select how far back you want to delete usage data. This action cannot be undone."}
+ "Select how far back you want to delete usage, request logs, and analytics data. Provider configuration, connections, API keys, combos, and settings are preserved. This action cannot be undone."}
0 ? 500 : 200 }
diff --git a/src/app/api/usage/analytics/route.ts b/src/app/api/usage/analytics/route.ts
index ca55440f39..f30e48e3c4 100644
--- a/src/app/api/usage/analytics/route.ts
+++ b/src/app/api/usage/analytics/route.ts
@@ -22,6 +22,7 @@ import {
getPresetCostModelRows,
} from "@/lib/db/usageAnalytics";
import { getFallbackStats } from "@/lib/db/callLogStats";
+import { buildByProviderRows } from "@/lib/usage/providerDisplayNames";
function getRangeStartIso(range: string): string | null {
const end = new Date();
@@ -668,19 +669,7 @@ export async function GET(request: Request) {
providerCostByProvider.set(provider, (providerCostByProvider.get(provider) || 0) + cost);
}
- const byProvider = providerRows.map((row) => ({
- provider: getProviderById(toStringValue(row.provider))?.name ?? toStringValue(row.provider),
- requests: Number(row.requests),
- promptTokens: Number(row.promptTokens),
- completionTokens: Number(row.completionTokens),
- totalTokens: Number(row.totalTokens),
- avgLatencyMs: Math.round(Number(row.avgLatencyMs)),
- successRatePct:
- Number(row.requests) > 0
- ? Number((Number(row.successfulRequests) / Number(row.requests)) * 100).toFixed(2)
- : 0,
- cost: roundCost(providerCostByProvider.get(toStringValue(row.provider)) || 0),
- }));
+ const byProvider = await buildByProviderRows(providerRows, providerCostByProvider);
const accountCostByAccount = new Map();
for (const row of accountCostRows) {
diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json
index c23daa0855..7f8f7935bd 100644
--- a/src/i18n/messages/en.json
+++ b/src/i18n/messages/en.json
@@ -5686,7 +5686,7 @@
"purgeLogsFailed": "Failed to purge logs",
"logsDeleted": "{count, plural, =0 {No expired logs purged} one {Purged # expired log} other {Purged # expired logs}}",
"resetUsageData": "Reset Usage Data",
- "resetUsageDataDesc": "Select how far back you want to delete usage data. This action cannot be undone.",
+ "resetUsageDataDesc": "Select how far back you want to delete usage, request logs, and analytics data. Provider configuration, connections, API keys, combos, and settings are preserved. This action cannot be undone.",
"resetUsagePeriod_5m": "5 minutes",
"resetUsagePeriod_1h": "1 hour",
"resetUsagePeriod_3h": "3 hours",
diff --git a/src/lib/db/cleanup.ts b/src/lib/db/cleanup.ts
index c3cf716280..fc866800b3 100644
--- a/src/lib/db/cleanup.ts
+++ b/src/lib/db/cleanup.ts
@@ -8,6 +8,13 @@ import { getDbInstance } from "./core";
import { getUserDatabaseSettings } from "./databaseSettings";
import { rollupUsageHistoryBeforeDate } from "@/lib/usage/aggregateHistory";
import { purgeCallLogArtifactDirectory } from "@/lib/usage/callLogArtifacts";
+import {
+ collectCallLogArtifactsBefore,
+ deleteAllFromTable,
+ deleteCallLogArtifacts,
+ deleteFromTableBefore,
+ type DeleteByPeriodTarget,
+} from "./cleanup/usagePurge";
interface CleanupResult {
deleted: number;
@@ -509,6 +516,16 @@ export interface ResetUsageHistoryResult extends CleanupResult {
deletedUsageHistory: number;
deletedDailySummary: number;
deletedHourlySummary: number;
+ deletedCallLogs: number;
+ deletedCallLogArtifacts: number;
+ deletedRequestDetailLogs: number;
+ deletedProxyLogs: number;
+ deletedRelayLogs: number;
+ deletedCompressionAnalytics: number;
+ deletedCompressionRunTelemetry: number;
+ deletedRoutingDecisions: number;
+ deletedQuotaConsumption: number;
+ deletedTokenLedger: number;
}
function isResetUsageHistoryPeriod(period: string): period is ResetUsageHistoryPeriod {
@@ -528,6 +545,21 @@ function isResetUsageHistoryPeriod(period: string): period is ResetUsageHistoryP
* every row in all three tables; any other value deletes rows strictly
* older than `now - period`. Throws on an invalid period.
*/
+const RESET_TARGETS: Array = [
+ { table: "usage_history", column: "timestamp", cutoff: "iso", resultKey: "deletedUsageHistory" },
+ { table: "daily_usage_summary", column: "date", cutoff: "date", resultKey: "deletedDailySummary" },
+ { table: "hourly_usage_summary", column: "date_hour", cutoff: "dateHour", resultKey: "deletedHourlySummary" },
+ { table: "call_logs", column: "timestamp", cutoff: "iso", resultKey: "deletedCallLogs" },
+ { table: "request_detail_logs", column: "timestamp", cutoff: "iso", resultKey: "deletedRequestDetailLogs" },
+ { table: "proxy_logs", column: "timestamp", cutoff: "iso", resultKey: "deletedProxyLogs" },
+ { table: "relay_logs", column: "created_at", cutoff: "epochSeconds", resultKey: "deletedRelayLogs" },
+ { table: "compression_analytics", column: "timestamp", cutoff: "iso", resultKey: "deletedCompressionAnalytics" },
+ { table: "compression_run_telemetry", column: "timestamp", cutoff: "epochMs", resultKey: "deletedCompressionRunTelemetry" },
+ { table: "routing_decisions", column: "created_at", cutoff: "iso", resultKey: "deletedRoutingDecisions" },
+ { table: "quota_consumption", column: "updated_at", cutoff: "epochMs", resultKey: "deletedQuotaConsumption" },
+ { table: "token_ledger", column: "created_at", cutoff: "iso", resultKey: "deletedTokenLedger" },
+];
+
export async function resetUsageHistory(period: string): Promise {
if (!isResetUsageHistoryPeriod(period)) {
throw new Error(`Invalid reset period: ${period}`);
@@ -539,53 +571,55 @@ export async function resetUsageHistory(period: string): Promise {
if (period === "all") {
- const usageHistory = db.prepare("DELETE FROM usage_history").run();
- const dailySummary = db.prepare("DELETE FROM daily_usage_summary").run();
- const hourlySummary = db.prepare("DELETE FROM hourly_usage_summary").run();
- result.deletedUsageHistory = usageHistory.changes;
- result.deletedDailySummary = dailySummary.changes;
- result.deletedHourlySummary = hourlySummary.changes;
+ for (const target of RESET_TARGETS) {
+ (result[target.resultKey] as number) = deleteAllFromTable(target.table);
+ }
return;
}
- const cutoffMs = Date.now() - RESET_USAGE_HISTORY_PERIOD_MS[period];
- const cutoffIso = new Date(cutoffMs).toISOString();
- // usage_history.timestamp is a full ISO string; daily_usage_summary.date is
- // "YYYY-MM-DD"; hourly_usage_summary.date_hour is "YYYY-MM-DD HH:00:00" (see
- // src/lib/usage/aggregateHistory.ts, which derives both with SQLite's UTC-based
- // DATE()/strftime()). Slicing the UTC ISO cutoff keeps all three comparisons
- // consistent without re-deriving timezone-sensitive date math by hand.
- const cutoffDate = cutoffIso.slice(0, 10);
- const cutoffDateHour = `${cutoffIso.slice(0, 10)} ${cutoffIso.slice(11, 13)}:00:00`;
-
- const usageHistory = db
- .prepare("DELETE FROM usage_history WHERE timestamp < ?")
- .run(cutoffIso);
- const dailySummary = db
- .prepare("DELETE FROM daily_usage_summary WHERE date < ?")
- .run(cutoffDate);
- const hourlySummary = db
- .prepare("DELETE FROM hourly_usage_summary WHERE date_hour < ?")
- .run(cutoffDateHour);
-
- result.deletedUsageHistory = usageHistory.changes;
- result.deletedDailySummary = dailySummary.changes;
- result.deletedHourlySummary = hourlySummary.changes;
+ const cutoffIso = new Date(Date.now() - RESET_USAGE_HISTORY_PERIOD_MS[period]).toISOString();
+ artifactsToDelete = collectCallLogArtifactsBefore(cutoffIso);
+ for (const target of RESET_TARGETS) {
+ (result[target.resultKey] as number) = deleteFromTableBefore(target, cutoffIso);
+ }
});
runReset();
- result.deleted =
- result.deletedUsageHistory + result.deletedDailySummary + result.deletedHourlySummary;
+
+ let artifactResult: { deletedArtifacts: number; errors: number };
+ if (period === "all") {
+ artifactResult = purgeCallLogArtifactDirectory();
+ } else {
+ artifactResult = deleteCallLogArtifacts(artifactsToDelete);
+ }
+ result.deletedCallLogArtifacts = artifactResult.deletedArtifacts;
+ result.deletedArtifacts = artifactResult.deletedArtifacts;
+ result.errors += artifactResult.errors;
+
+ result.deleted = RESET_TARGETS.reduce((sum, t) => sum + (result[t.resultKey] as number), 0);
console.log(
- `[Cleanup] Reset usage data (period=${period}): ${result.deletedUsageHistory} usage_history, ` +
- `${result.deletedDailySummary} daily_usage_summary, ${result.deletedHourlySummary} hourly_usage_summary`
+ `[Cleanup] Reset usage/log data (period=${period}): ${result.deleted} row(s), ` +
+ `${result.deletedCallLogArtifacts} call log artifact(s)`
);
} catch (err: unknown) {
console.error("[Cleanup] Error resetting usage history:", err);
diff --git a/src/lib/db/cleanup/usagePurge.ts b/src/lib/db/cleanup/usagePurge.ts
new file mode 100644
index 0000000000..e73d54fd65
--- /dev/null
+++ b/src/lib/db/cleanup/usagePurge.ts
@@ -0,0 +1,83 @@
+/**
+ * Low-level table/artifact purge primitives backing {@link resetUsageHistory}
+ * (see `../cleanup.ts`). Split out of `cleanup.ts` to keep that file under the
+ * repo's file-size cap — these helpers have no state of their own beyond the
+ * shared DB singleton, so they are safe to call from any reset routine that
+ * needs generic "wipe" / "wipe before cutoff" semantics.
+ *
+ * @module lib/db/cleanup/usagePurge
+ */
+import { getDbInstance } from "../core";
+import { cleanupEmptyCallLogDirs, deleteCallArtifact } from "@/lib/usage/callLogArtifacts";
+
+export type DeleteByPeriodTarget = {
+ table: string;
+ column: string;
+ cutoff: "iso" | "date" | "dateHour" | "epochMs" | "epochSeconds";
+};
+
+export function tableExists(table: string): boolean {
+ const row = getDbInstance()
+ .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?")
+ .get(table) as { name?: string } | undefined;
+ return Boolean(row?.name);
+}
+
+export function deleteAllFromTable(table: string): number {
+ if (!tableExists(table)) return 0;
+ return getDbInstance().prepare(`DELETE FROM ${table}`).run().changes;
+}
+
+export function deleteFromTableBefore(target: DeleteByPeriodTarget, cutoffIso: string): number {
+ if (!tableExists(target.table)) return 0;
+
+ const cutoff = (() => {
+ switch (target.cutoff) {
+ case "date":
+ return cutoffIso.slice(0, 10);
+ case "dateHour":
+ return `${cutoffIso.slice(0, 10)} ${cutoffIso.slice(11, 13)}:00:00`;
+ case "epochMs":
+ return new Date(cutoffIso).getTime();
+ case "epochSeconds":
+ return Math.floor(new Date(cutoffIso).getTime() / 1000);
+ case "iso":
+ default:
+ return cutoffIso;
+ }
+ })();
+
+ return getDbInstance()
+ .prepare(`DELETE FROM ${target.table} WHERE ${target.column} < ?`)
+ .run(cutoff).changes;
+}
+
+export function collectCallLogArtifactsBefore(cutoffIso: string): string[] {
+ if (!tableExists("call_logs")) return [];
+
+ const rows = getDbInstance()
+ .prepare(
+ "SELECT artifact_relpath FROM call_logs WHERE timestamp < ? AND artifact_relpath IS NOT NULL"
+ )
+ .all(cutoffIso) as Array<{ artifact_relpath?: string | null }>;
+
+ return rows
+ .map((row) => row.artifact_relpath)
+ .filter((relPath): relPath is string => typeof relPath === "string" && relPath.length > 0);
+}
+
+export function deleteCallLogArtifacts(relativePaths: string[]): {
+ deletedArtifacts: number;
+ errors: number;
+} {
+ const result = { deletedArtifacts: 0, errors: 0 };
+
+ for (const relPath of new Set(relativePaths)) {
+ if (deleteCallArtifact(relPath)) {
+ result.deletedArtifacts++;
+ }
+ }
+
+ cleanupEmptyCallLogDirs();
+ return result;
+}
diff --git a/src/lib/usage/providerDisplayNames.ts b/src/lib/usage/providerDisplayNames.ts
new file mode 100644
index 0000000000..c5e66ea451
--- /dev/null
+++ b/src/lib/usage/providerDisplayNames.ts
@@ -0,0 +1,88 @@
+/**
+ * Provider display-name resolution for the usage analytics `byProvider`
+ * breakdown (`src/app/api/usage/analytics/route.ts`).
+ *
+ * Raw `usage_history.provider` values are internal provider ids (e.g. a
+ * dynamic compatible-provider uuid-suffixed id). This module maps those ids
+ * to the friendly `name`/`prefix` configured on the matching `provider_nodes`
+ * row, falling back to the raw id when no node matches, so analytics rows
+ * show a readable label instead of an internal id.
+ *
+ * @module lib/usage/providerDisplayNames
+ */
+import { getProviderNodes } from "@/models";
+import { getProviderById } from "@/shared/constants/providers";
+
+function toStringValue(value: unknown, fallback = ""): string {
+ return typeof value === "string" && value.trim().length > 0 ? value : fallback;
+}
+
+function roundCost(value: number): number {
+ return Math.round(value * 1_000_000) / 1_000_000;
+}
+
+function getProviderDisplayName(
+ provider: unknown,
+ providerDisplayNames: Map
+): string {
+ const rawProvider = toStringValue(provider, "unknown");
+ // Configured node name wins; static catalog covers built-ins (e.g. codex →
+ // "OpenAI Codex") the nodes table doesn't know about; raw id is the last resort.
+ return (
+ providerDisplayNames.get(rawProvider) || getProviderById(rawProvider)?.name || rawProvider
+ );
+}
+
+async function getProviderDisplayNames(): Promise> {
+ const displayNames = new Map();
+ const providerNodes = (await getProviderNodes()) as Array<{
+ id?: unknown;
+ name?: unknown;
+ prefix?: unknown;
+ }>;
+
+ for (const node of providerNodes) {
+ const id = toStringValue(node.id);
+ if (!id) continue;
+
+ const displayName = toStringValue(node.name) || toStringValue(node.prefix) || id;
+ displayNames.set(id, displayName);
+ }
+
+ return displayNames;
+}
+
+export interface ByProviderRow {
+ provider: string;
+ requests: number;
+ promptTokens: number;
+ completionTokens: number;
+ totalTokens: number;
+ avgLatencyMs: number;
+ successRatePct: number | string;
+ cost: number;
+}
+
+/**
+ * Builds the `byProvider` analytics rows, resolving each row's raw provider
+ * id to its configured display name.
+ */
+export async function buildByProviderRows(
+ providerRows: Array>,
+ providerCostByProvider: Map
+): Promise {
+ const providerDisplayNames = await getProviderDisplayNames();
+ return providerRows.map((row) => ({
+ provider: getProviderDisplayName(row.provider, providerDisplayNames),
+ requests: Number(row.requests),
+ promptTokens: Number(row.promptTokens),
+ completionTokens: Number(row.completionTokens),
+ totalTokens: Number(row.totalTokens),
+ avgLatencyMs: Math.round(Number(row.avgLatencyMs)),
+ successRatePct:
+ Number(row.requests) > 0
+ ? Number((Number(row.successfulRequests) / Number(row.requests)) * 100).toFixed(2)
+ : 0,
+ cost: roundCost(providerCostByProvider.get(toStringValue(row.provider)) || 0),
+ }));
+}
diff --git a/tests/unit/usage-history-reset.test.ts b/tests/unit/usage-history-reset.test.ts
index 0050ee90b6..352473365f 100644
--- a/tests/unit/usage-history-reset.test.ts
+++ b/tests/unit/usage-history-reset.test.ts
@@ -40,8 +40,10 @@ function teardown() {
}
}
-function countRows(db: import("better-sqlite3").Database, table: string): number {
- const row = db.prepare(`SELECT COUNT(*) as c FROM ${table}`).get() as { c: number };
+function countRows(db: unknown, table: string): number {
+ const row = (db as { prepare: (sql: string) => { get: () => unknown } })
+ .prepare(`SELECT COUNT(*) as c FROM ${table}`)
+ .get() as { c: number };
return row.c;
}
@@ -71,8 +73,33 @@ test("resetUsageHistory: 'all' wipes usage_history, daily_usage_summary, and hou
const recentDate = recentIso.slice(0, 10);
const oldDateHour = `${oldIso.slice(0, 10)} ${oldIso.slice(11, 13)}:00:00`;
const recentDateHour = `${recentIso.slice(0, 10)} ${recentIso.slice(11, 13)}:00:00`;
+ const oldArtifactRelpath = "2026-01-01/old-call.json";
+ const recentArtifactRelpath = "2026-01-01/recent-call.json";
+ const oldArtifactPath = path.join(tempDir, "call_logs", oldArtifactRelpath);
+ const recentArtifactPath = path.join(tempDir, "call_logs", recentArtifactRelpath);
+
+ fs.mkdirSync(path.dirname(oldArtifactPath), { recursive: true });
+ fs.writeFileSync(oldArtifactPath, "{}");
+ fs.writeFileSync(recentArtifactPath, "{}");
function seed() {
+ db.prepare(
+ "INSERT INTO provider_nodes (id, type, name, prefix, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)"
+ ).run("openai-compatible-chat-test", "chat", "Custom Test", "custom-test", recentIso, recentIso);
+ db.prepare("INSERT INTO api_keys (id, name, key, created_at) VALUES (?, ?, ?, ?)").run(
+ "key-test",
+ "Test Key",
+ "sk-test",
+ recentIso
+ );
+ db.prepare("INSERT INTO combos (id, name, data, created_at, updated_at) VALUES (?, ?, ?, ?, ?)").run(
+ "combo-test",
+ "Test Combo",
+ "{}",
+ recentIso,
+ recentIso
+ );
+
db.prepare(
"INSERT INTO usage_history (provider, model, timestamp) VALUES (?, ?, ?)"
).run("openai", "gpt-test", oldIso);
@@ -80,6 +107,33 @@ test("resetUsageHistory: 'all' wipes usage_history, daily_usage_summary, and hou
"INSERT INTO usage_history (provider, model, timestamp) VALUES (?, ?, ?)"
).run("openai", "gpt-test", recentIso);
+ db.prepare("INSERT INTO call_logs (id, timestamp, artifact_relpath) VALUES (?, ?, ?)").run(
+ "old-call",
+ oldIso,
+ oldArtifactRelpath
+ );
+ db.prepare("INSERT INTO call_logs (id, timestamp, artifact_relpath) VALUES (?, ?, ?)").run(
+ "recent-call",
+ recentIso,
+ recentArtifactRelpath
+ );
+ db.prepare("INSERT INTO request_detail_logs (id, timestamp) VALUES (?, ?)").run(
+ "old-detail",
+ oldIso
+ );
+ db.prepare("INSERT INTO request_detail_logs (id, timestamp) VALUES (?, ?)").run(
+ "recent-detail",
+ recentIso
+ );
+ db.prepare("INSERT INTO proxy_logs (id, timestamp) VALUES (?, ?)").run("old-proxy", oldIso);
+ db.prepare("INSERT INTO proxy_logs (id, timestamp) VALUES (?, ?)").run("recent-proxy", recentIso);
+ db.prepare(
+ "INSERT INTO compression_analytics (timestamp, mode, original_tokens, compressed_tokens, tokens_saved) VALUES (?, ?, ?, ?, ?)"
+ ).run(oldIso, "lite", 100, 50, 50);
+ db.prepare(
+ "INSERT INTO compression_analytics (timestamp, mode, original_tokens, compressed_tokens, tokens_saved) VALUES (?, ?, ?, ?, ?)"
+ ).run(recentIso, "lite", 100, 50, 50);
+
db.prepare(
"INSERT INTO daily_usage_summary (provider, model, date) VALUES (?, ?, ?)"
).run("openai", "gpt-test", oldDate);
@@ -98,6 +152,18 @@ test("resetUsageHistory: 'all' wipes usage_history, daily_usage_summary, and hou
seed();
assert.equal(countRows(db, "usage_history"), 2, "sanity: 2 usage_history rows seeded");
+ assert.equal(countRows(db, "call_logs"), 2, "sanity: 2 call_logs rows seeded");
+ assert.equal(
+ countRows(db, "request_detail_logs"),
+ 2,
+ "sanity: 2 request_detail_logs rows seeded"
+ );
+ assert.equal(countRows(db, "proxy_logs"), 2, "sanity: 2 proxy_logs rows seeded");
+ assert.equal(
+ countRows(db, "compression_analytics"),
+ 2,
+ "sanity: 2 compression_analytics rows seeded"
+ );
assert.equal(
countRows(db, "daily_usage_summary"),
2,
@@ -114,6 +180,18 @@ test("resetUsageHistory: 'all' wipes usage_history, daily_usage_summary, and hou
assert.equal(periodResult.errors, 0, "period reset should not report errors");
assert.equal(periodResult.deletedUsageHistory, 1, "should delete only the old usage_history row");
+ assert.equal(periodResult.deletedCallLogs, 1, "should delete only the old call_logs row");
+ assert.equal(
+ periodResult.deletedRequestDetailLogs,
+ 1,
+ "should delete only the old request_detail_logs row"
+ );
+ assert.equal(periodResult.deletedProxyLogs, 1, "should delete only the old proxy_logs row");
+ assert.equal(
+ periodResult.deletedCompressionAnalytics,
+ 1,
+ "should delete only the old compression_analytics row"
+ );
assert.equal(
periodResult.deletedDailySummary,
1,
@@ -124,9 +202,28 @@ test("resetUsageHistory: 'all' wipes usage_history, daily_usage_summary, and hou
1,
"should delete only the old hourly_usage_summary row"
);
- assert.equal(periodResult.deleted, 3, "total deleted should sum the three tables");
+ assert.equal(periodResult.deleted, 7, "total deleted should sum the reset tables");
+ assert.equal(periodResult.deletedCallLogArtifacts, 1, "period reset should delete only old call artifact");
+ assert.equal(fs.existsSync(oldArtifactPath), false, "period reset should delete old call artifact");
+ assert.equal(fs.existsSync(recentArtifactPath), true, "period reset should preserve recent call artifact");
+
+ assert.equal(countRows(db, "provider_nodes"), 1, "provider config should survive reset");
+ assert.equal(countRows(db, "api_keys"), 1, "API keys should survive reset");
+ assert.equal(countRows(db, "combos"), 1, "combos should survive reset");
assert.equal(countRows(db, "usage_history"), 1, "recent usage_history row should survive");
+ assert.equal(countRows(db, "call_logs"), 1, "recent call_logs row should survive");
+ assert.equal(
+ countRows(db, "request_detail_logs"),
+ 1,
+ "recent request_detail_logs row should survive"
+ );
+ assert.equal(countRows(db, "proxy_logs"), 1, "recent proxy_logs row should survive");
+ assert.equal(
+ countRows(db, "compression_analytics"),
+ 1,
+ "recent compression_analytics row should survive"
+ );
assert.equal(
countRows(db, "daily_usage_summary"),
1,
@@ -152,6 +249,18 @@ test("resetUsageHistory: 'all' wipes usage_history, daily_usage_summary, and hou
assert.equal(allResult.errors, 0, "'all' reset should not report errors");
assert.equal(allResult.deletedUsageHistory, 1, "'all' should delete the remaining usage_history row");
+ assert.equal(allResult.deletedCallLogs, 1, "'all' should delete the remaining call_logs row");
+ assert.equal(
+ allResult.deletedRequestDetailLogs,
+ 1,
+ "'all' should delete the remaining request_detail_logs row"
+ );
+ assert.equal(allResult.deletedProxyLogs, 1, "'all' should delete the remaining proxy_logs row");
+ assert.equal(
+ allResult.deletedCompressionAnalytics,
+ 1,
+ "'all' should delete the remaining compression_analytics row"
+ );
assert.equal(
allResult.deletedDailySummary,
1,
@@ -162,10 +271,19 @@ test("resetUsageHistory: 'all' wipes usage_history, daily_usage_summary, and hou
1,
"'all' should delete the remaining hourly_usage_summary row"
);
+ assert.equal(allResult.deletedCallLogArtifacts, 1, "'all' should delete remaining call artifact");
+ assert.equal(fs.existsSync(recentArtifactPath), false, "'all' should delete recent call artifact");
assert.equal(countRows(db, "usage_history"), 0, "'all' should empty usage_history");
+ assert.equal(countRows(db, "call_logs"), 0, "'all' should empty call_logs");
+ assert.equal(countRows(db, "request_detail_logs"), 0, "'all' should empty request_detail_logs");
+ assert.equal(countRows(db, "proxy_logs"), 0, "'all' should empty proxy_logs");
+ assert.equal(countRows(db, "compression_analytics"), 0, "'all' should empty compression_analytics");
assert.equal(countRows(db, "daily_usage_summary"), 0, "'all' should empty daily_usage_summary");
assert.equal(countRows(db, "hourly_usage_summary"), 0, "'all' should empty hourly_usage_summary");
+ assert.equal(countRows(db, "provider_nodes"), 1, "provider config should still survive 'all'");
+ assert.equal(countRows(db, "api_keys"), 1, "API keys should still survive 'all'");
+ assert.equal(countRows(db, "combos"), 1, "combos should still survive 'all'");
// 3) An invalid period throws instead of silently doing nothing / deleting everything.
await assert.rejects(