@uploadista/flow-security-nodes 0.0.20 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,22 +1,21 @@
1
1
 
2
- > @uploadista/flow-security-nodes@0.0.20-beta.9 build /Users/denislaboureyras/Documents/uploadista/dev/uploadista-workspace/uploadista-sdk/packages/flow/security/nodes
2
+ > @uploadista/flow-security-nodes@0.1.0 build /home/runner/work/uploadista-sdk/uploadista-sdk/packages/flow/security/nodes
3
3
  > tsc --noEmit && tsdown
4
4
 
5
- ℹ tsdown v0.18.0 powered by rolldown v1.0.0-beta.53
6
- ℹ config file: /Users/denislaboureyras/Documents/uploadista/dev/uploadista-workspace/uploadista-sdk/packages/flow/security/nodes/tsdown.config.ts
5
+ ℹ tsdown v0.19.0 powered by rolldown v1.0.0-beta.59
6
+ ℹ config file: /home/runner/work/uploadista-sdk/uploadista-sdk/packages/flow/security/nodes/tsdown.config.ts
7
7
  ℹ entry: src/index.ts
8
8
  ℹ tsconfig: tsconfig.json
9
9
  ℹ Build start
10
- ℹ Cleaning 7 files
11
10
  ℹ [CJS] dist/index.cjs 1.23 kB │ gzip: 0.69 kB
12
11
  ℹ [CJS] 1 files, total: 1.23 kB
13
12
  ℹ [ESM] dist/index.mjs 1.26 kB │ gzip: 0.73 kB
14
- ℹ [ESM] dist/index.mjs.map 5.67 kB │ gzip: 2.19 kB
13
+ ℹ [ESM] dist/index.mjs.map 5.64 kB │ gzip: 2.19 kB
15
14
  ℹ [ESM] dist/index.d.mts.map 0.54 kB │ gzip: 0.29 kB
16
15
  ℹ [ESM] dist/index.d.mts 3.29 kB │ gzip: 1.21 kB
17
- ℹ [ESM] 4 files, total: 10.76 kB
18
- ✔ Build complete in 7869ms
16
+ ℹ [ESM] 4 files, total: 10.73 kB
17
+ ✔ Build complete in 25058ms
19
18
  ℹ [CJS] dist/index.d.cts.map 0.54 kB │ gzip: 0.29 kB
20
19
  ℹ [CJS] dist/index.d.cts 3.29 kB │ gzip: 1.20 kB
21
20
  ℹ [CJS] 2 files, total: 3.83 kB
22
- ✔ Build complete in 7894ms
21
+ ✔ Build complete in 25074ms
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/scan-virus-node.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;cAaa,YAAU,CAAA,CAAA;;;AAAvB,CAAA,CAAA;AACY,KAAA,UAAA,GAAa,CAAA,CAAE,KAAa,CAAA,OAAA,UAAR,CAAA;AAKhC;;;cAAa,iBAAe,CAAA,CAAA;;;;EAAA,CAAA,CAAA,CAAA;EAAA,OAAA,cAAA,cAAA,YAAA,CAAA,CAAA;AAgB5B,CAAA,eAAY,CAAA;AA8BI,KA9BJ,eAAA,GAAkB,CAAA,CAAE,KA8BG,CAAA,OA9BU,eA8BV,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAnB,mBAAA,sBAEN;;IAC0B,MAAA,CAAA,OAD0B,sBAAA,CAC1B,YAAA;yBAAA,uBAAA,CAAA,UAAA;;;;;;;aAqE2l1M;;iEAAA,uBAAA,CAAA,UAAA"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/scan-virus-node.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;cAaa,YAAU,CAAA,CAAA;;;AAAvB,CAAA,CAAA;AACY,KAAA,UAAA,GAAa,CAAA,CAAE,KAAa,CAAA,OAAA,UAAR,CAAA;AAKhC;;;cAAa,iBAAe,CAAA,CAAA;;;;EAAA,CAAA,CAAA,CAAA;EAAA,OAAA,cAAA,cAAA,YAAA,CAAA,CAAA;AAgB5B,CAAA,eAAY,CAAA;AA8BI,KA9BJ,eAAA,GAAkB,CAAA,CAAE,KA8BG,CAAA,OA9BU,eA8BV,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAnB,mBAAA,sBAEN;;IAC0B,MAAA,CAAA,OAD0B,sBAAA,CAC1B,YAAA;yBAAA,uBAAA,CAAA,UAAA;;;;;;;aAqEik5M;;iEAAA,uBAAA,CAAA,UAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/scan-virus-node.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;cAaa,YAAU,CAAA,CAAA;;;AAAvB,CAAA,CAAA;AACY,KAAA,UAAA,GAAa,CAAA,CAAE,KAAa,CAAA,OAAA,UAAR,CAAA;AAKhC;;;cAAa,iBAAe,CAAA,CAAA;;;;EAAA,CAAA,CAAA,CAAA;EAAA,OAAA,cAAA,cAAA,YAAA,CAAA,CAAA;AAgB5B,CAAA,eAAY,CAAA;AA8BI,KA9BJ,eAAA,GAAkB,CAAA,CAAE,KA8BG,CAAA,OA9BU,eA8BV,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAnB,mBAAA,sBAEN;;IAC0B,MAAA,CAAA,OAD0B,sBAAA,CAC1B,YAAA;yBAAA,uBAAA,CAAA,UAAA;;;;;;;aAqE2l1M;;iEAAA,uBAAA,CAAA,UAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/scan-virus-node.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;cAaa,YAAU,CAAA,CAAA;;;AAAvB,CAAA,CAAA;AACY,KAAA,UAAA,GAAa,CAAA,CAAE,KAAa,CAAA,OAAA,UAAR,CAAA;AAKhC;;;cAAa,iBAAe,CAAA,CAAA;;;;EAAA,CAAA,CAAA,CAAA;EAAA,OAAA,cAAA,cAAA,YAAA,CAAA,CAAA;AAgB5B,CAAA,eAAY,CAAA;AA8BI,KA9BJ,eAAA,GAAkB,CAAA,CAAE,KA8BG,CAAA,OA9BU,eA8BV,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAnB,mBAAA,sBAEN;;IAC0B,MAAA,CAAA,OAD0B,sBAAA,CAC1B,YAAA;yBAAA,uBAAA,CAAA,UAAA;;;;;;;aAqEik5M;;iEAAA,uBAAA,CAAA,UAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["scanMetadata: ScanMetadata"],"sources":["../src/scan-virus-node.ts"],"sourcesContent":["import { httpFailure } from \"@uploadista/core/errors\";\nimport {\n createTransformNode,\n type ScanMetadata,\n STORAGE_OUTPUT_TYPE_ID,\n VirusScanPlugin,\n} from \"@uploadista/core/flow\";\nimport { Effect } from \"effect\";\nimport { z } from \"zod\";\n\n/**\n * Scan action to take when a virus is detected\n */\nexport const ScanAction = z.enum([\"fail\", \"pass\"]);\nexport type ScanAction = z.infer<typeof ScanAction>;\n\n/**\n * Parameters for the Scan Virus node\n */\nexport const ScanVirusParams = z.object({\n /**\n * Action to take when a virus is detected:\n * - \"fail\": Mark flow task as FAILED and stop processing\n * - \"pass\": Continue processing but add virus metadata to file\n */\n action: ScanAction.default(\"fail\"),\n\n /**\n * Maximum time to wait for scan completion (in milliseconds)\n * Default: 60000ms (60 seconds)\n * Max: 300000ms (5 minutes)\n */\n timeout: z.number().min(1000).max(300000).optional().default(60000),\n});\n\nexport type ScanVirusParams = z.infer<typeof ScanVirusParams>;\n\n/**\n * Creates a Scan Virus node for malware detection\n *\n * Scans files for viruses and malware using the configured VirusScanPlugin\n * (typically ClamAV). Supports configurable actions on detection:\n * - \"fail\": Stop flow execution when virus detected\n * - \"pass\": Continue flow with detection metadata\n *\n * All scan results are stored in file.metadata.virusScan for downstream nodes.\n *\n * @param id - Unique node identifier\n * @param params - Configuration parameters for scan behavior\n * @returns Effect that resolves to the configured node\n *\n * @example\n * ```typescript\n * // Fail on virus detection (default)\n * const failNode = yield* createScanVirusNode(\"scan-1\", {\n * action: \"fail\"\n * });\n *\n * // Pass through with metadata\n * const passNode = yield* createScanVirusNode(\"scan-2\", {\n * action: \"pass\",\n * timeout: 120000 // 2 minutes\n * });\n * ```\n */\nexport function createScanVirusNode(\n id: string,\n params: ScanVirusParams = { action: \"fail\", timeout: 60000 },\n options?: { keepOutput?: boolean },\n) {\n return Effect.gen(function* () {\n const virusScanService = yield* VirusScanPlugin;\n\n // Validate params\n const validatedParams = ScanVirusParams.parse(params);\n\n return yield* createTransformNode({\n id,\n name: \"Scan Virus\",\n description: \"Scans files for viruses and malware using ClamAV\",\n nodeTypeId: \"scan-virus\",\n outputTypeId: STORAGE_OUTPUT_TYPE_ID,\n keepOutput: options?.keepOutput,\n // External service - enable circuit breaker with fail fallback (don't skip security scans)\n circuitBreaker: {\n enabled: true,\n failureThreshold: 5,\n resetTimeout: 60000,\n fallback: { type: \"fail\" },\n },\n transform: (inputBytes, file) =>\n Effect.gen(function* () {\n // Perform virus scan\n const scanResult = yield* virusScanService.scan(inputBytes);\n\n // Get engine version for metadata\n const engineVersion = yield* virusScanService.getVersion();\n\n // Build comprehensive scan metadata\n const scanMetadata: ScanMetadata = {\n scanned: true,\n isClean: scanResult.isClean,\n detectedViruses: scanResult.detectedViruses,\n scanDate: new Date().toISOString(),\n engineVersion,\n definitionsDate: new Date().toISOString(), // TODO: Get actual definitions date from plugin\n };\n\n // Check if virus was detected\n if (!scanResult.isClean) {\n // Build error message with detected viruses\n const virusList = scanResult.detectedViruses.join(\", \");\n const message = `Virus detected: ${virusList}`;\n\n // Handle based on configured action\n if (validatedParams.action === \"fail\") {\n // Fail the flow task\n return yield* httpFailure(\"VIRUS_DETECTED\", {\n body: message,\n details: { scanMetadata },\n });\n }\n // action === \"pass\": Continue with metadata (handled below)\n }\n\n // Return file with scan metadata (clean or pass action)\n return {\n bytes: inputBytes, // Pass through original bytes unchanged\n metadata: {\n ...file.metadata,\n virusScan: scanMetadata,\n },\n };\n }),\n });\n });\n}\n"],"mappings":"2NAaA,MAAa,EAAa,EAAE,KAAK,CAAC,OAAQ,OAAO,CAAC,CAMrC,EAAkB,EAAE,OAAO,CAMtC,OAAQ,EAAW,QAAQ,OAAO,CAOlC,QAAS,EAAE,QAAQ,CAAC,IAAI,IAAK,CAAC,IAAI,IAAO,CAAC,UAAU,CAAC,QAAQ,IAAM,CACpE,CAAC,CAgCF,SAAgB,EACd,EACA,EAA0B,CAAE,OAAQ,OAAQ,QAAS,IAAO,CAC5D,EACA,CACA,OAAO,EAAO,IAAI,WAAa,CAC7B,IAAM,EAAmB,MAAO,EAG1B,EAAkB,EAAgB,MAAM,EAAO,CAErD,OAAO,MAAO,EAAoB,CAChC,KACA,KAAM,aACN,YAAa,mDACb,WAAY,aACZ,aAAc,EACd,WAAY,GAAS,WAErB,eAAgB,CACd,QAAS,GACT,iBAAkB,EAClB,aAAc,IACd,SAAU,CAAE,KAAM,OAAQ,CAC3B,CACD,WAAY,EAAY,IACtB,EAAO,IAAI,WAAa,CAEtB,IAAM,EAAa,MAAO,EAAiB,KAAK,EAAW,CAGrD,EAAgB,MAAO,EAAiB,YAAY,CAGpDA,EAA6B,CACjC,QAAS,GACT,QAAS,EAAW,QACpB,gBAAiB,EAAW,gBAC5B,SAAU,IAAI,MAAM,CAAC,aAAa,CAClC,gBACA,gBAAiB,IAAI,MAAM,CAAC,aAAa,CAC1C,CAGD,GAAI,CAAC,EAAW,QAAS,CAGvB,IAAM,EAAU,mBADE,EAAW,gBAAgB,KAAK,KAAK,GAIvD,GAAI,EAAgB,SAAW,OAE7B,OAAO,MAAO,EAAY,iBAAkB,CAC1C,KAAM,EACN,QAAS,CAAE,eAAc,CAC1B,CAAC,CAMN,MAAO,CACL,MAAO,EACP,SAAU,CACR,GAAG,EAAK,SACR,UAAW,EACZ,CACF,EACD,CACL,CAAC,EACF"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/scan-virus-node.ts"],"sourcesContent":["import { httpFailure } from \"@uploadista/core/errors\";\nimport {\n createTransformNode,\n type ScanMetadata,\n STORAGE_OUTPUT_TYPE_ID,\n VirusScanPlugin,\n} from \"@uploadista/core/flow\";\nimport { Effect } from \"effect\";\nimport { z } from \"zod\";\n\n/**\n * Scan action to take when a virus is detected\n */\nexport const ScanAction = z.enum([\"fail\", \"pass\"]);\nexport type ScanAction = z.infer<typeof ScanAction>;\n\n/**\n * Parameters for the Scan Virus node\n */\nexport const ScanVirusParams = z.object({\n /**\n * Action to take when a virus is detected:\n * - \"fail\": Mark flow task as FAILED and stop processing\n * - \"pass\": Continue processing but add virus metadata to file\n */\n action: ScanAction.default(\"fail\"),\n\n /**\n * Maximum time to wait for scan completion (in milliseconds)\n * Default: 60000ms (60 seconds)\n * Max: 300000ms (5 minutes)\n */\n timeout: z.number().min(1000).max(300000).optional().default(60000),\n});\n\nexport type ScanVirusParams = z.infer<typeof ScanVirusParams>;\n\n/**\n * Creates a Scan Virus node for malware detection\n *\n * Scans files for viruses and malware using the configured VirusScanPlugin\n * (typically ClamAV). Supports configurable actions on detection:\n * - \"fail\": Stop flow execution when virus detected\n * - \"pass\": Continue flow with detection metadata\n *\n * All scan results are stored in file.metadata.virusScan for downstream nodes.\n *\n * @param id - Unique node identifier\n * @param params - Configuration parameters for scan behavior\n * @returns Effect that resolves to the configured node\n *\n * @example\n * ```typescript\n * // Fail on virus detection (default)\n * const failNode = yield* createScanVirusNode(\"scan-1\", {\n * action: \"fail\"\n * });\n *\n * // Pass through with metadata\n * const passNode = yield* createScanVirusNode(\"scan-2\", {\n * action: \"pass\",\n * timeout: 120000 // 2 minutes\n * });\n * ```\n */\nexport function createScanVirusNode(\n id: string,\n params: ScanVirusParams = { action: \"fail\", timeout: 60000 },\n options?: { keepOutput?: boolean },\n) {\n return Effect.gen(function* () {\n const virusScanService = yield* VirusScanPlugin;\n\n // Validate params\n const validatedParams = ScanVirusParams.parse(params);\n\n return yield* createTransformNode({\n id,\n name: \"Scan Virus\",\n description: \"Scans files for viruses and malware using ClamAV\",\n nodeTypeId: \"scan-virus\",\n outputTypeId: STORAGE_OUTPUT_TYPE_ID,\n keepOutput: options?.keepOutput,\n // External service - enable circuit breaker with fail fallback (don't skip security scans)\n circuitBreaker: {\n enabled: true,\n failureThreshold: 5,\n resetTimeout: 60000,\n fallback: { type: \"fail\" },\n },\n transform: (inputBytes, file) =>\n Effect.gen(function* () {\n // Perform virus scan\n const scanResult = yield* virusScanService.scan(inputBytes);\n\n // Get engine version for metadata\n const engineVersion = yield* virusScanService.getVersion();\n\n // Build comprehensive scan metadata\n const scanMetadata: ScanMetadata = {\n scanned: true,\n isClean: scanResult.isClean,\n detectedViruses: scanResult.detectedViruses,\n scanDate: new Date().toISOString(),\n engineVersion,\n definitionsDate: new Date().toISOString(), // TODO: Get actual definitions date from plugin\n };\n\n // Check if virus was detected\n if (!scanResult.isClean) {\n // Build error message with detected viruses\n const virusList = scanResult.detectedViruses.join(\", \");\n const message = `Virus detected: ${virusList}`;\n\n // Handle based on configured action\n if (validatedParams.action === \"fail\") {\n // Fail the flow task\n return yield* httpFailure(\"VIRUS_DETECTED\", {\n body: message,\n details: { scanMetadata },\n });\n }\n // action === \"pass\": Continue with metadata (handled below)\n }\n\n // Return file with scan metadata (clean or pass action)\n return {\n bytes: inputBytes, // Pass through original bytes unchanged\n metadata: {\n ...file.metadata,\n virusScan: scanMetadata,\n },\n };\n }),\n });\n });\n}\n"],"mappings":"2NAaA,MAAa,EAAa,EAAE,KAAK,CAAC,OAAQ,OAAO,CAAC,CAMrC,EAAkB,EAAE,OAAO,CAMtC,OAAQ,EAAW,QAAQ,OAAO,CAOlC,QAAS,EAAE,QAAQ,CAAC,IAAI,IAAK,CAAC,IAAI,IAAO,CAAC,UAAU,CAAC,QAAQ,IAAM,CACpE,CAAC,CAgCF,SAAgB,EACd,EACA,EAA0B,CAAE,OAAQ,OAAQ,QAAS,IAAO,CAC5D,EACA,CACA,OAAO,EAAO,IAAI,WAAa,CAC7B,IAAM,EAAmB,MAAO,EAG1B,EAAkB,EAAgB,MAAM,EAAO,CAErD,OAAO,MAAO,EAAoB,CAChC,KACA,KAAM,aACN,YAAa,mDACb,WAAY,aACZ,aAAc,EACd,WAAY,GAAS,WAErB,eAAgB,CACd,QAAS,GACT,iBAAkB,EAClB,aAAc,IACd,SAAU,CAAE,KAAM,OAAQ,CAC3B,CACD,WAAY,EAAY,IACtB,EAAO,IAAI,WAAa,CAEtB,IAAM,EAAa,MAAO,EAAiB,KAAK,EAAW,CAGrD,EAAgB,MAAO,EAAiB,YAAY,CAGpD,EAA6B,CACjC,QAAS,GACT,QAAS,EAAW,QACpB,gBAAiB,EAAW,gBAC5B,SAAU,IAAI,MAAM,CAAC,aAAa,CAClC,gBACA,gBAAiB,IAAI,MAAM,CAAC,aAAa,CAC1C,CAGD,GAAI,CAAC,EAAW,QAAS,CAGvB,IAAM,EAAU,mBADE,EAAW,gBAAgB,KAAK,KAAK,GAIvD,GAAI,EAAgB,SAAW,OAE7B,OAAO,MAAO,EAAY,iBAAkB,CAC1C,KAAM,EACN,QAAS,CAAE,eAAc,CAC1B,CAAC,CAMN,MAAO,CACL,MAAO,EACP,SAAU,CACR,GAAG,EAAK,SACR,UAAW,EACZ,CACF,EACD,CACL,CAAC,EACF"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@uploadista/flow-security-nodes",
3
3
  "type": "module",
4
- "version": "0.0.20",
4
+ "version": "0.1.0",
5
5
  "description": "Security processing nodes for Uploadista Flow",
6
6
  "license": "MIT",
7
7
  "author": "Uploadista",
@@ -14,7 +14,7 @@
14
14
  }
15
15
  },
16
16
  "dependencies": {
17
- "@uploadista/core": "0.0.20"
17
+ "@uploadista/core": "0.1.0"
18
18
  },
19
19
  "peerDependencies": {
20
20
  "effect": "^3.0.0",
@@ -22,19 +22,19 @@
22
22
  },
23
23
  "devDependencies": {
24
24
  "@effect/vitest": "0.27.0",
25
- "@types/node": "24.10.4",
26
- "effect": "3.19.12",
27
- "tsdown": "0.18.0",
28
- "vitest": "4.0.15",
29
- "zod": "4.2.0",
30
- "@uploadista/typescript-config": "0.0.20"
25
+ "@types/node": "24.10.8",
26
+ "effect": "3.19.14",
27
+ "tsdown": "0.19.0",
28
+ "vitest": "4.0.17",
29
+ "zod": "4.3.5",
30
+ "@uploadista/typescript-config": "0.1.0"
31
31
  },
32
32
  "scripts": {
33
33
  "build": "tsc --noEmit && tsdown",
34
34
  "format": "biome format --write ./src",
35
35
  "lint": "biome lint --write ./src",
36
36
  "check": "biome check --write ./src",
37
- "test": "vitest",
37
+ "test": "vitest run",
38
38
  "test:run": "vitest run",
39
39
  "test:watch": "vitest watch"
40
40
  }
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, it } from "@effect/vitest";
2
2
  import { UploadistaError } from "@uploadista/core/errors";
3
3
  import {
4
- TestUploadServer,
4
+ TestUploadEngine,
5
5
  TestVirusScanPlugin,
6
6
  } from "@uploadista/core/testing";
7
7
  import type { UploadFile } from "@uploadista/core/types";
@@ -56,7 +56,7 @@ const createInfectedFileBytes = (): Uint8Array => {
56
56
  /**
57
57
  * Test layer combining all mocks
58
58
  */
59
- const TestLayer = Layer.mergeAll(TestVirusScanPlugin, TestUploadServer);
59
+ const TestLayer = Layer.mergeAll(TestVirusScanPlugin, TestUploadEngine);
60
60
 
61
61
  describe("Scan Virus Node", () => {
62
62
  describe("Node Creation", () => {
@@ -64,6 +64,7 @@ describe("Scan Virus Node", () => {
64
64
  Effect.gen(function* () {
65
65
  const node = yield* createScanVirusNode("scan-1", {
66
66
  action: "fail",
67
+ timeout: 60000,
67
68
  });
68
69
 
69
70
  expect(node.id).toBe("scan-1");
@@ -89,6 +90,7 @@ describe("Scan Virus Node", () => {
89
90
  Effect.gen(function* () {
90
91
  const node = yield* createScanVirusNode("scan-clean", {
91
92
  action: "fail",
93
+ timeout: 60000,
92
94
  });
93
95
 
94
96
  const testFile = createTestUploadFile();
@@ -117,6 +119,7 @@ describe("Scan Virus Node", () => {
117
119
  Effect.gen(function* () {
118
120
  const node = yield* createScanVirusNode("scan-metadata", {
119
121
  action: "fail",
122
+ timeout: 60000,
120
123
  });
121
124
 
122
125
  const testFile = createTestUploadFile();
@@ -147,6 +150,7 @@ describe("Scan Virus Node", () => {
147
150
  Effect.gen(function* () {
148
151
  const node = yield* createScanVirusNode("scan-preserve", {
149
152
  action: "fail",
153
+ timeout: 60000,
150
154
  });
151
155
 
152
156
  const testFile = createTestUploadFile({
@@ -182,41 +186,43 @@ describe("Scan Virus Node", () => {
182
186
  });
183
187
 
184
188
  describe("Infected File Scanning - Fail Action", () => {
185
- it.effect(
186
- "should fail flow when virus detected with fail action",
187
- () =>
188
- Effect.gen(function* () {
189
- const node = yield* createScanVirusNode("scan-fail", {
190
- action: "fail",
191
- });
189
+ it.effect("should fail flow when virus detected with fail action", () =>
190
+ Effect.gen(function* () {
191
+ const node = yield* createScanVirusNode("scan-fail", {
192
+ action: "fail",
193
+ timeout: 60000,
194
+ });
192
195
 
193
- const testFile = createTestUploadFile();
194
-
195
- const result = yield* Effect.either(
196
- node.run({
197
- data: testFile,
198
- jobId: "test-job",
199
- flowId: "test-flow",
200
- storageId: "test-storage",
201
- clientId: "test-client",
202
- }),
203
- );
204
-
205
- expect(result._tag).toBe("Left");
206
- if (result._tag === "Left") {
207
- expect(result.left).toBeInstanceOf(UploadistaError);
208
- expect(result.left.code).toBe("VIRUS_DETECTED");
209
- }
210
- }).pipe(Effect.provide(TestLayer)),
196
+ // Use file ID containing "infected" to trigger EICAR content from mock
197
+ const testFile = createTestUploadFile({ id: "infected-file-1" });
198
+
199
+ const result = yield* Effect.either(
200
+ node.run({
201
+ data: testFile,
202
+ jobId: "test-job",
203
+ flowId: "test-flow",
204
+ storageId: "test-storage",
205
+ clientId: "test-client",
206
+ }),
207
+ );
208
+
209
+ expect(result._tag).toBe("Left");
210
+ if (result._tag === "Left") {
211
+ expect(result.left).toBeInstanceOf(UploadistaError);
212
+ expect(result.left.code).toBe("VIRUS_DETECTED");
213
+ }
214
+ }).pipe(Effect.provide(TestLayer)),
211
215
  );
212
216
 
213
217
  it.effect("should include virus names in error message", () =>
214
218
  Effect.gen(function* () {
215
219
  const node = yield* createScanVirusNode("scan-names", {
216
220
  action: "fail",
221
+ timeout: 60000,
217
222
  });
218
223
 
219
- const testFile = createTestUploadFile();
224
+ // Use file ID containing "infected" to trigger EICAR content from mock
225
+ const testFile = createTestUploadFile({ id: "infected-file-2" });
220
226
 
221
227
  const result = yield* Effect.either(
222
228
  node.run({
@@ -241,9 +247,11 @@ describe("Scan Virus Node", () => {
241
247
  Effect.gen(function* () {
242
248
  const node = yield* createScanVirusNode("scan-details", {
243
249
  action: "fail",
250
+ timeout: 60000,
244
251
  });
245
252
 
246
- const testFile = createTestUploadFile();
253
+ // Use file ID containing "infected" to trigger EICAR content from mock
254
+ const testFile = createTestUploadFile({ id: "infected-file-3" });
247
255
 
248
256
  const result = yield* Effect.either(
249
257
  node.run({
@@ -262,35 +270,35 @@ describe("Scan Virus Node", () => {
262
270
  expect(error.details?.scanMetadata).toBeDefined();
263
271
  expect(error.details?.scanMetadata.isClean).toBe(false);
264
272
  expect(error.details?.scanMetadata.detectedViruses).toBeDefined();
265
- expect(error.details?.scanMetadata.detectedViruses.length).toBeGreaterThan(
266
- 0,
267
- );
273
+ expect(
274
+ error.details?.scanMetadata.detectedViruses.length,
275
+ ).toBeGreaterThan(0);
268
276
  }
269
277
  }).pipe(Effect.provide(TestLayer)),
270
278
  );
271
279
  });
272
280
 
273
281
  describe("Infected File Scanning - Pass Action", () => {
274
- it.effect(
275
- "should continue flow when virus detected with pass action",
276
- () =>
277
- Effect.gen(function* () {
278
- const node = yield* createScanVirusNode("scan-pass", {
279
- action: "pass",
280
- });
282
+ it.effect("should continue flow when virus detected with pass action", () =>
283
+ Effect.gen(function* () {
284
+ const node = yield* createScanVirusNode("scan-pass", {
285
+ action: "pass",
286
+ timeout: 60000,
287
+ });
281
288
 
282
- const testFile = createTestUploadFile();
289
+ // Use file ID containing "infected" to trigger EICAR content from mock
290
+ const testFile = createTestUploadFile({ id: "infected-file-pass-1" });
283
291
 
284
- const result = yield* node.run({
285
- data: testFile,
286
- jobId: "test-job",
287
- flowId: "test-flow",
288
- storageId: "test-storage",
289
- clientId: "test-client",
290
- });
292
+ const result = yield* node.run({
293
+ data: testFile,
294
+ jobId: "test-job",
295
+ flowId: "test-flow",
296
+ storageId: "test-storage",
297
+ clientId: "test-client",
298
+ });
291
299
 
292
- expect(result.type).toBe("complete");
293
- }).pipe(Effect.provide(TestLayer)),
300
+ expect(result.type).toBe("complete");
301
+ }).pipe(Effect.provide(TestLayer)),
294
302
  );
295
303
 
296
304
  it.effect(
@@ -299,9 +307,11 @@ describe("Scan Virus Node", () => {
299
307
  Effect.gen(function* () {
300
308
  const node = yield* createScanVirusNode("scan-pass-metadata", {
301
309
  action: "pass",
310
+ timeout: 60000,
302
311
  });
303
312
 
304
- const testFile = createTestUploadFile();
313
+ // Use file ID containing "infected" to trigger EICAR content from mock
314
+ const testFile = createTestUploadFile({ id: "infected-file-pass-2" });
305
315
 
306
316
  const result = yield* node.run({
307
317
  data: testFile,
@@ -327,9 +337,11 @@ describe("Scan Virus Node", () => {
327
337
  Effect.gen(function* () {
328
338
  const node = yield* createScanVirusNode("scan-pass-bytes", {
329
339
  action: "pass",
340
+ timeout: 60000,
330
341
  });
331
342
 
332
- const testFile = createTestUploadFile();
343
+ // Use file ID containing "infected" to trigger EICAR content from mock
344
+ const testFile = createTestUploadFile({ id: "infected-file-pass-3" });
333
345
 
334
346
  const result = yield* node.run({
335
347
  data: testFile,
@@ -342,7 +354,9 @@ describe("Scan Virus Node", () => {
342
354
  expect(result.type).toBe("complete");
343
355
  if (result.type === "complete") {
344
356
  expect(result.data).toBeDefined();
345
- expect(result.data.id).toBe(testFile.id);
357
+ // The transform creates a new upload with a new ID, but the file is processed
358
+ expect(result.data.id).toBeDefined();
359
+ expect(typeof result.data.id).toBe("string");
346
360
  }
347
361
  }).pipe(Effect.provide(TestLayer)),
348
362
  );
@@ -375,6 +389,7 @@ describe("Scan Virus Node", () => {
375
389
  Effect.gen(function* () {
376
390
  const node = yield* createScanVirusNode("scan-default-timeout", {
377
391
  action: "fail",
392
+ timeout: 60000,
378
393
  });
379
394
 
380
395
  expect(node.id).toBe("scan-default-timeout");
@@ -398,6 +413,7 @@ describe("Scan Virus Node", () => {
398
413
  Effect.gen(function* () {
399
414
  const node = yield* createScanVirusNode("scan-version", {
400
415
  action: "fail",
416
+ timeout: 60000,
401
417
  });
402
418
 
403
419
  const testFile = createTestUploadFile();
@@ -1,5 +0,0 @@
1
-
2
- > @uploadista/flow-security-nodes@0.0.17 test /Users/denislaboureyras/Documents/uploadista/dev/uploadista-workspace/uploadista-sdk/packages/flow/security/nodes
3
- > vitest
4
-
5
-  ELIFECYCLE  Test failed. See above for more details.