| 294 | } |
| 295 | |
| 296 | export class TestStore implements Store { |
| 297 | path: string; |
| 298 | private mutex: Promise<void> = Promise.resolve(); |
| 299 | |
| 300 | constructor() { |
| 301 | const path = process.env.MGREP_TEST_STORE_PATH; |
| 302 | if (!path) { |
| 303 | throw new Error("MGREP_TEST_STORE_PATH is not set"); |
| 304 | } |
| 305 | this.path = path; |
| 306 | } |
| 307 | |
| 308 | private async synchronized<T>(fn: () => Promise<T>): Promise<T> { |
| 309 | let unlock: () => void = () => {}; |
| 310 | const newLock = new Promise<void>((resolve) => { |
| 311 | unlock = resolve; |
| 312 | }); |
| 313 | |
| 314 | const previousLock = this.mutex; |
| 315 | this.mutex = newLock; |
| 316 | |
| 317 | await previousLock; |
| 318 | |
| 319 | try { |
| 320 | return await fn(); |
| 321 | } finally { |
| 322 | unlock(); |
| 323 | } |
| 324 | } |
| 325 | |
| 326 | private async load(): Promise<TestStoreDB> { |
| 327 | try { |
| 328 | const content = await fs.readFile(this.path, "utf-8"); |
| 329 | return JSON.parse(content); |
| 330 | } catch { |
| 331 | return { |
| 332 | info: { |
| 333 | name: "Test Store", |
| 334 | description: "A test store", |
| 335 | created_at: new Date().toISOString(), |
| 336 | updated_at: new Date().toISOString(), |
| 337 | counts: { pending: 0, in_progress: 0 }, |
| 338 | }, |
| 339 | files: {}, |
| 340 | }; |
| 341 | } |
| 342 | } |
| 343 | |
| 344 | private async save(data: TestStoreDB): Promise<void> { |
| 345 | await fs.writeFile(this.path, JSON.stringify(data, null, 2)); |
| 346 | } |
| 347 | |
| 348 | private async readContent(file: File | ReadableStream): Promise<string> { |
| 349 | if ( |
| 350 | "text" in file && |
| 351 | typeof (file as { text: unknown }).text === "function" |
| 352 | ) { |
| 353 | return await (file as File).text(); |
nothing calls this directly
no outgoing calls
no test coverage detected