1import { describe, expect, it } from "vitest";
2import {
3 STALE_DRIFT_THRESHOLD,
4 adjustWorkspaceStorageBytes,
5 authoritativeStoredBytes,
6 recomputeWorkspaceStorageBytes,
7 effectiveStorageQuota,
8 PLAN_USAGE_LIMITS,
9 type QuotaPrisma,
10 type QuotaPrismaFull,
11} from "../quota.js";
12import { parseFileKey, buildFileKey } from "@factory/storage";
13
14
25
26interface Fixture {
27 usage: bigint;
28}
29
30describe("adjustWorkspaceStorageBytes (event counter)", () => {
31 it("complete: +size (950 MB → 990 MB after a 40 MB complete)", async () => {
32 const fx: Fixture = { usage: BigInt(950 * 1024 * 1024) };
33
34
35 const prisma = {
36 fileVersion: { aggregate: async () => ({ _sum: { size: 0 } }) },
37 workspace: {
38 findUnique: async () => ({ usageStorageBytes: fx.usage }),
39 update: async (args: any) => {
40 fx.usage = args.data.usageStorageBytes;
41 return {};
42 },
43 },
44 } as unknown as QuotaPrisma;
45 const next = await adjustWorkspaceStorageBytes(prisma, "ws_1", BigInt(40 * 1024 * 1024));
46 expect(next).toBe(BigInt(990 * 1024 * 1024));
47 expect(fx.usage).toBe(BigInt(990 * 1024 * 1024));
48 });
49
50 it("prune/purge: −Σ (never below the 0 floor)", async () => {
51 const fx: Fixture = { usage: BigInt(50 * 1024 * 1024) };
52 const prisma = {
53 fileVersion: { aggregate: async () => ({ _sum: { size: 0 } }) },
54 workspace: {
55 findUnique: async () => ({ usageStorageBytes: fx.usage }),
56 update: async (args: any) => {
57 fx.usage = args.data.usageStorageBytes;
58 return {};
59 },
60 },
61 } as unknown as QuotaPrisma;
62
63 const next = await adjustWorkspaceStorageBytes(prisma, "ws_1", -BigInt(80 * 1024 * 1024));
64 expect(next).toBe(0n);
65 expect(fx.usage).toBe(0n);
66 });
67
68 it("restore: ±0 (bytes were retained while trashed)", async () => {
69 const fx: Fixture = { usage: BigInt(123) };
70 const prisma = {
71 fileVersion: { aggregate: async () => ({ _sum: { size: 0 } }) },
72 workspace: {
73 findUnique: async () => ({ usageStorageBytes: fx.usage }),
74 update: async (args: any) => {
75 fx.usage = args.data.usageStorageBytes;
76 return {};
77 },
78 },
79 } as unknown as QuotaPrisma;
80 await adjustWorkspaceStorageBytes(prisma, "ws_1", 0n);
81 expect(fx.usage).toBe(BigInt(123));
82 });
83});
84
85describe("authoritativeStoredBytes (the denial-boundary sum)", () => {
86 it("= Σ FileVersion.size (non-deleted) + Σ SCANNING File.size", async () => {
87
88
89 const prisma = {
90 fileVersion: { aggregate: async () => ({ _sum: { size: 100 * 1024 * 1024 } }) },
91 file: {
92 findMany: async () => [
93 { id: "f1", size: 20 * 1024 * 1024 },
94 ],
95 },
96 workspace: { findUnique: async () => null, update: async () => ({}) },
97 } as unknown as QuotaPrismaFull;
98 const total = await authoritativeStoredBytes(prisma, "ws_1");
99 expect(total).toBe(BigInt(120 * 1024 * 1024));
100 });
101
102 it("excludes non-SCANNING files (the findMany where is honored by the SUT)", async () => {
103
104
105 const prisma = {
106 fileVersion: { aggregate: async () => ({ _sum: { size: 5 } }) },
107 file: { findMany: async () => [] },
108 workspace: { findUnique: async () => null, update: async () => ({}) },
109 } as unknown as QuotaPrismaFull;
110 const total = await authoritativeStoredBytes(prisma, "ws_1");
111 expect(total).toBe(5n);
112 });
113});
114
115describe("recomputeWorkspaceStorageBytes (reconciler-only)", () => {
116 it("sets the counter to the FileVersion ledger sum", async () => {
117 let written: bigint | null = null;
118 const prisma = {
119 fileVersion: { aggregate: async () => ({ _sum: { size: 777 } }) },
120 workspace: {
121 findUnique: async () => ({ usageStorageBytes: 0n }),
122 update: async (args: any) => {
123 written = args.data.usageStorageBytes;
124 return {};
125 },
126 },
127 } as unknown as QuotaPrisma;
128 const total = await recomputeWorkspaceStorageBytes(prisma, "ws_1");
129 expect(total).toBe(777n);
130 expect(written).toBe(777n);
131 });
132});
133
134describe("version swap logic (parseFileKey ⇄ buildFileKey)", () => {
135 it("a replaceFileId upload's key encodes n+1, and parseFileKey recovers it", () => {
136 const base = buildFileKey({
137 workspaceId: "ws_1",
138 fileId: "file_abc",
139 version: 1,
140 name: "report.pdf",
141 });
142 const v2 = buildFileKey({
143 workspaceId: "ws_1",
144 fileId: "file_abc",
145 version: 2,
146 name: "report-v2.pdf",
147 });
148 expect(parseFileKey(base).version).toBe(1);
149 expect(parseFileKey(v2).version).toBe(2);
150
151
152
153 expect(parseFileKey(v2).fileId).toBe("file_abc");
154 expect(parseFileKey(v2).workspaceId).toBe("ws_1");
155 });
156
157 it("effectiveStorageQuota = min(plan, override) — the storage bar denominator", () => {
158 expect(effectiveStorageQuota("free", null)).toBe(PLAN_USAGE_LIMITS.free!.storageQuota);
159 expect(effectiveStorageQuota("paid", null)).toBe(PLAN_USAGE_LIMITS.paid!.storageQuota);
160
161
162 expect(effectiveStorageQuota("paid", { storageQuota: 2 * 1024 ** 3 })).toBe(2 * 1024 ** 3);
163 expect(effectiveStorageQuota("free", { storageQuota: 99 * 1024 ** 3 })).toBe(
164 PLAN_USAGE_LIMITS.free!.storageQuota
165 );
166
167 expect(effectiveStorageQuota("free", { storageQuota: 0 })).toBe(
168 PLAN_USAGE_LIMITS.free!.storageQuota
169 );
170 });
171
172 it("the 1 GB stale-drift threshold is exactly 1024^3", () => {
173 expect(STALE_DRIFT_THRESHOLD).toBe(1024 ** 3);
174 });
175});
176