@skillsmith/core 0.7.2 → 0.8.1
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.
- package/CHANGELOG.md +19 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/analytics/ROIDashboardService.compute.d.ts +17 -0
- package/dist/src/analytics/ROIDashboardService.compute.d.ts.map +1 -0
- package/dist/src/analytics/ROIDashboardService.compute.js +134 -0
- package/dist/src/analytics/ROIDashboardService.compute.js.map +1 -0
- package/dist/src/analytics/ROIDashboardService.d.ts +0 -8
- package/dist/src/analytics/ROIDashboardService.d.ts.map +1 -1
- package/dist/src/analytics/ROIDashboardService.js +9 -138
- package/dist/src/analytics/ROIDashboardService.js.map +1 -1
- package/dist/src/api/client.types.d.ts +5 -0
- package/dist/src/api/client.types.d.ts.map +1 -1
- package/dist/src/api/index.d.ts +1 -0
- package/dist/src/api/index.d.ts.map +1 -1
- package/dist/src/api/index.js +4 -0
- package/dist/src/api/index.js.map +1 -1
- package/dist/src/api/schemas.compatibility.test.d.ts +11 -0
- package/dist/src/api/schemas.compatibility.test.d.ts.map +1 -0
- package/dist/src/api/schemas.compatibility.test.js +38 -0
- package/dist/src/api/schemas.compatibility.test.js.map +1 -0
- package/dist/src/api/schemas.d.ts +16 -9
- package/dist/src/api/schemas.d.ts.map +1 -1
- package/dist/src/api/schemas.js +13 -4
- package/dist/src/api/schemas.js.map +1 -1
- package/dist/src/api/schemas.license.test.d.ts +11 -0
- package/dist/src/api/schemas.license.test.d.ts.map +1 -0
- package/dist/src/api/schemas.license.test.js +51 -0
- package/dist/src/api/schemas.license.test.js.map +1 -0
- package/dist/src/api/types.d.ts +18 -3
- package/dist/src/api/types.d.ts.map +1 -1
- package/dist/src/api/types.js +29 -2
- package/dist/src/api/types.js.map +1 -1
- package/dist/src/audit/remote-audit.d.ts +41 -0
- package/dist/src/audit/remote-audit.d.ts.map +1 -1
- package/dist/src/audit/remote-audit.js +71 -28
- package/dist/src/audit/remote-audit.js.map +1 -1
- package/dist/src/audit/remote-audit.test.js +89 -1
- package/dist/src/audit/remote-audit.test.js.map +1 -1
- package/dist/src/compatibility/slugs.d.ts +34 -0
- package/dist/src/compatibility/slugs.d.ts.map +1 -0
- package/dist/src/compatibility/slugs.js +57 -0
- package/dist/src/compatibility/slugs.js.map +1 -0
- package/dist/src/db/schema-sql.d.ts +1 -1
- package/dist/src/db/schema-sql.d.ts.map +1 -1
- package/dist/src/db/schema-sql.js +1 -1
- package/dist/src/embeddings/probe.d.ts +62 -0
- package/dist/src/embeddings/probe.d.ts.map +1 -0
- package/dist/src/embeddings/probe.js +89 -0
- package/dist/src/embeddings/probe.js.map +1 -0
- package/dist/src/exports/types.d.ts +1 -0
- package/dist/src/exports/types.d.ts.map +1 -1
- package/dist/src/exports/types.js +4 -0
- package/dist/src/exports/types.js.map +1 -1
- package/dist/src/index.d.ts +6 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/indexer/GitHubIndexer.d.ts.map +1 -1
- package/dist/src/indexer/GitHubIndexer.js +1 -1
- package/dist/src/indexer/GitHubIndexer.js.map +1 -1
- package/dist/src/provenance/SourceRecoveryService.d.ts +48 -0
- package/dist/src/provenance/SourceRecoveryService.d.ts.map +1 -0
- package/dist/src/provenance/SourceRecoveryService.js +182 -0
- package/dist/src/provenance/SourceRecoveryService.js.map +1 -0
- package/dist/src/provenance/backfill.d.ts +47 -0
- package/dist/src/provenance/backfill.d.ts.map +1 -0
- package/dist/src/provenance/backfill.js +206 -0
- package/dist/src/provenance/backfill.js.map +1 -0
- package/dist/src/provenance/git-config.d.ts +22 -0
- package/dist/src/provenance/git-config.d.ts.map +1 -0
- package/dist/src/provenance/git-config.js +117 -0
- package/dist/src/provenance/git-config.js.map +1 -0
- package/dist/src/provenance/index.d.ts +12 -0
- package/dist/src/provenance/index.d.ts.map +1 -0
- package/dist/src/provenance/index.js +12 -0
- package/dist/src/provenance/index.js.map +1 -0
- package/dist/src/provenance/local-skill-scan.d.ts +35 -0
- package/dist/src/provenance/local-skill-scan.d.ts.map +1 -0
- package/dist/src/provenance/local-skill-scan.js +79 -0
- package/dist/src/provenance/local-skill-scan.js.map +1 -0
- package/dist/src/provenance/plugin-manifest.d.ts +16 -0
- package/dist/src/provenance/plugin-manifest.d.ts.map +1 -0
- package/dist/src/provenance/plugin-manifest.js +52 -0
- package/dist/src/provenance/plugin-manifest.js.map +1 -0
- package/dist/src/provenance/types.d.ts +86 -0
- package/dist/src/provenance/types.d.ts.map +1 -0
- package/dist/src/provenance/types.js +21 -0
- package/dist/src/provenance/types.js.map +1 -0
- package/dist/src/repositories/SkillRepository.d.ts.map +1 -1
- package/dist/src/repositories/SkillRepository.js +3 -1
- package/dist/src/repositories/SkillRepository.js.map +1 -1
- package/dist/src/scripts/__tests__/scan-imported-skills.test.js +10 -0
- package/dist/src/scripts/__tests__/scan-imported-skills.test.js.map +1 -1
- package/dist/src/scripts/validation/normalizers.d.ts.map +1 -1
- package/dist/src/scripts/validation/normalizers.js +9 -7
- package/dist/src/scripts/validation/normalizers.js.map +1 -1
- package/dist/src/scripts/validation/types.d.ts +8 -2
- package/dist/src/scripts/validation/types.d.ts.map +1 -1
- package/dist/src/scripts/validation/types.js +10 -2
- package/dist/src/scripts/validation/types.js.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.d.ts +0 -7
- package/dist/src/security/scanner/SecurityScanner.d.ts.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.exec.d.ts +56 -0
- package/dist/src/security/scanner/SecurityScanner.exec.d.ts.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.exec.js +255 -0
- package/dist/src/security/scanner/SecurityScanner.exec.js.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.helpers.d.ts.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.helpers.js +17 -1
- package/dist/src/security/scanner/SecurityScanner.helpers.js.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.js +34 -199
- package/dist/src/security/scanner/SecurityScanner.js.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.scanners.d.ts +22 -0
- package/dist/src/security/scanner/SecurityScanner.scanners.d.ts.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.scanners.js +213 -0
- package/dist/src/security/scanner/SecurityScanner.scanners.js.map +1 -0
- package/dist/src/security/scanner/index.d.ts +1 -1
- package/dist/src/security/scanner/index.d.ts.map +1 -1
- package/dist/src/security/scanner/index.js +1 -1
- package/dist/src/security/scanner/index.js.map +1 -1
- package/dist/src/security/scanner/patterns.d.ts +21 -0
- package/dist/src/security/scanner/patterns.d.ts.map +1 -1
- package/dist/src/security/scanner/patterns.js +34 -0
- package/dist/src/security/scanner/patterns.js.map +1 -1
- package/dist/src/security/scanner/types.d.ts +3 -1
- package/dist/src/security/scanner/types.d.ts.map +1 -1
- package/dist/src/security/scanner/weights.d.ts.map +1 -1
- package/dist/src/security/scanner/weights.js +6 -0
- package/dist/src/security/scanner/weights.js.map +1 -1
- package/dist/src/services/SearchService.helpers.d.ts.map +1 -1
- package/dist/src/services/SearchService.helpers.js +2 -0
- package/dist/src/services/SearchService.helpers.js.map +1 -1
- package/dist/src/services/SearchService.types.d.ts +1 -0
- package/dist/src/services/SearchService.types.d.ts.map +1 -1
- package/dist/src/services/skill-installation.helpers.d.ts +2 -26
- package/dist/src/services/skill-installation.helpers.d.ts.map +1 -1
- package/dist/src/services/skill-installation.helpers.js +2 -120
- package/dist/src/services/skill-installation.helpers.js.map +1 -1
- package/dist/src/services/skill-installation.io.d.ts +43 -0
- package/dist/src/services/skill-installation.io.d.ts.map +1 -0
- package/dist/src/services/skill-installation.io.js +176 -0
- package/dist/src/services/skill-installation.io.js.map +1 -0
- package/dist/src/services/skill-installation.service.d.ts.map +1 -1
- package/dist/src/services/skill-installation.service.js +51 -113
- package/dist/src/services/skill-installation.service.js.map +1 -1
- package/dist/src/services/skill-installation.types.d.ts.map +1 -1
- package/dist/src/services/skill-installation.types.js +9 -0
- package/dist/src/services/skill-installation.types.js.map +1 -1
- package/dist/src/services/skill-installation.validate.d.ts +44 -0
- package/dist/src/services/skill-installation.validate.d.ts.map +1 -0
- package/dist/src/services/skill-installation.validate.js +134 -0
- package/dist/src/services/skill-installation.validate.js.map +1 -0
- package/dist/src/telemetry/index.d.ts +2 -1
- package/dist/src/telemetry/index.d.ts.map +1 -1
- package/dist/src/telemetry/index.js +4 -1
- package/dist/src/telemetry/index.js.map +1 -1
- package/dist/src/telemetry/posthog.d.ts +19 -1
- package/dist/src/telemetry/posthog.d.ts.map +1 -1
- package/dist/src/telemetry/posthog.js +14 -0
- package/dist/src/telemetry/posthog.js.map +1 -1
- package/dist/src/telemetry/wrap.bench.d.ts +13 -0
- package/dist/src/telemetry/wrap.bench.d.ts.map +1 -0
- package/dist/src/telemetry/wrap.bench.js +41 -0
- package/dist/src/telemetry/wrap.bench.js.map +1 -0
- package/dist/src/telemetry/wrap.d.ts +75 -0
- package/dist/src/telemetry/wrap.d.ts.map +1 -0
- package/dist/src/telemetry/wrap.js +124 -0
- package/dist/src/telemetry/wrap.js.map +1 -0
- package/dist/src/telemetry/wrap.test.d.ts +14 -0
- package/dist/src/telemetry/wrap.test.d.ts.map +1 -0
- package/dist/src/telemetry/wrap.test.js +283 -0
- package/dist/src/telemetry/wrap.test.js.map +1 -0
- package/dist/src/types/skill.d.ts +6 -1
- package/dist/src/types/skill.d.ts.map +1 -1
- package/dist/src/types/skill.js.map +1 -1
- package/dist/src/types.d.ts +25 -1
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +3 -1
- package/dist/src/types.js.map +1 -1
- package/dist/tests/GitHubIndexer.edge-cases.test.js +2 -2
- package/dist/tests/GitHubIndexer.repository.test.d.ts +10 -0
- package/dist/tests/GitHubIndexer.repository.test.d.ts.map +1 -0
- package/dist/tests/GitHubIndexer.repository.test.js +190 -0
- package/dist/tests/GitHubIndexer.repository.test.js.map +1 -0
- package/dist/tests/GitHubIndexer.test.d.ts +2 -1
- package/dist/tests/GitHubIndexer.test.d.ts.map +1 -1
- package/dist/tests/GitHubIndexer.test.js +5 -183
- package/dist/tests/GitHubIndexer.test.js.map +1 -1
- package/dist/tests/QuarantineRepository.review-state-machine.test.d.ts +14 -0
- package/dist/tests/QuarantineRepository.review-state-machine.test.d.ts.map +1 -0
- package/dist/tests/QuarantineRepository.review-state-machine.test.js +296 -0
- package/dist/tests/QuarantineRepository.review-state-machine.test.js.map +1 -0
- package/dist/tests/RawUrlSourceAdapter.security.test.js +3 -2
- package/dist/tests/RawUrlSourceAdapter.security.test.js.map +1 -1
- package/dist/tests/SecurityScanner.exec.test.d.ts +11 -0
- package/dist/tests/SecurityScanner.exec.test.d.ts.map +1 -0
- package/dist/tests/SecurityScanner.exec.test.js +190 -0
- package/dist/tests/SecurityScanner.exec.test.js.map +1 -0
- package/dist/tests/SwarmIndexer.test.js +2 -2
- package/dist/tests/api/client.validation.test.js +7 -4
- package/dist/tests/api/client.validation.test.js.map +1 -1
- package/dist/tests/embeddings/probe.test.d.ts +13 -0
- package/dist/tests/embeddings/probe.test.d.ts.map +1 -0
- package/dist/tests/embeddings/probe.test.js +154 -0
- package/dist/tests/embeddings/probe.test.js.map +1 -0
- package/dist/tests/fixtures/api-responses/index.js +1 -1
- package/dist/tests/fixtures/api-responses/index.js.map +1 -1
- package/dist/tests/integration/QuarantineService.concurrency.test.d.ts +34 -0
- package/dist/tests/integration/QuarantineService.concurrency.test.d.ts.map +1 -0
- package/dist/tests/integration/QuarantineService.concurrency.test.js +280 -0
- package/dist/tests/integration/QuarantineService.concurrency.test.js.map +1 -0
- package/dist/tests/integration/openapi-contract.test.d.ts +16 -0
- package/dist/tests/integration/openapi-contract.test.d.ts.map +1 -0
- package/dist/tests/integration/openapi-contract.test.js +231 -0
- package/dist/tests/integration/openapi-contract.test.js.map +1 -0
- package/dist/tests/provenance/SourceRecoveryService.test.d.ts +2 -0
- package/dist/tests/provenance/SourceRecoveryService.test.d.ts.map +1 -0
- package/dist/tests/provenance/SourceRecoveryService.test.js +132 -0
- package/dist/tests/provenance/SourceRecoveryService.test.js.map +1 -0
- package/dist/tests/provenance/backfill.test.d.ts +2 -0
- package/dist/tests/provenance/backfill.test.d.ts.map +1 -0
- package/dist/tests/provenance/backfill.test.js +278 -0
- package/dist/tests/provenance/backfill.test.js.map +1 -0
- package/dist/tests/provenance/git-config.test.d.ts +2 -0
- package/dist/tests/provenance/git-config.test.d.ts.map +1 -0
- package/dist/tests/provenance/git-config.test.js +73 -0
- package/dist/tests/provenance/git-config.test.js.map +1 -0
- package/dist/tests/provenance/local-skill-scan.test.d.ts +2 -0
- package/dist/tests/provenance/local-skill-scan.test.d.ts.map +1 -0
- package/dist/tests/provenance/local-skill-scan.test.js +51 -0
- package/dist/tests/provenance/local-skill-scan.test.js.map +1 -0
- package/dist/tests/provenance/plugin-manifest.test.d.ts +2 -0
- package/dist/tests/provenance/plugin-manifest.test.d.ts.map +1 -0
- package/dist/tests/provenance/plugin-manifest.test.js +49 -0
- package/dist/tests/provenance/plugin-manifest.test.js.map +1 -0
- package/dist/tests/scripts/import-to-database.test.js +7 -1
- package/dist/tests/scripts/import-to-database.test.js.map +1 -1
- package/dist/tests/security/scanner-regression-guard.test.js +5 -1
- package/dist/tests/security/scanner-regression-guard.test.js.map +1 -1
- package/dist/tests/security.test.js +45 -0
- package/dist/tests/security.test.js.map +1 -1
- package/dist/tests/services/aidefence-feedback.test.js +2 -0
- package/dist/tests/services/aidefence-feedback.test.js.map +1 -1
- package/dist/tests/services/dep-quarantine-check.test.js +1 -1
- package/dist/tests/services/dep-quarantine-check.test.js.map +1 -1
- package/dist/tests/skill-scanner/allowlist.test.js +8 -1
- package/dist/tests/skill-scanner/allowlist.test.js.map +1 -1
- package/dist/tests/unit/services/skill-installation.gap1.test.d.ts +12 -0
- package/dist/tests/unit/services/skill-installation.gap1.test.d.ts.map +1 -0
- package/dist/tests/unit/services/skill-installation.gap1.test.js +96 -0
- package/dist/tests/unit/services/skill-installation.gap1.test.js.map +1 -0
- package/dist/tests/unit/services/skill-installation.io.test.d.ts +11 -0
- package/dist/tests/unit/services/skill-installation.io.test.d.ts.map +1 -0
- package/dist/tests/unit/services/skill-installation.io.test.js +130 -0
- package/dist/tests/unit/services/skill-installation.io.test.js.map +1 -0
- package/dist/tests/validate-skills.dedup.test.d.ts +5 -0
- package/dist/tests/validate-skills.dedup.test.d.ts.map +1 -0
- package/dist/tests/validate-skills.dedup.test.js +241 -0
- package/dist/tests/validate-skills.dedup.test.js.map +1 -0
- package/dist/tests/validate-skills.normalizers.test.d.ts +5 -0
- package/dist/tests/validate-skills.normalizers.test.d.ts.map +1 -0
- package/dist/tests/validate-skills.normalizers.test.js +109 -0
- package/dist/tests/validate-skills.normalizers.test.js.map +1 -0
- package/dist/tests/validate-skills.test.d.ts +1 -1
- package/dist/tests/validate-skills.test.js +7 -345
- package/dist/tests/validate-skills.test.js.map +1 -1
- package/package.json +29 -8
|
@@ -305,7 +305,7 @@ describe('GitHubIndexer', () => {
|
|
|
305
305
|
expect(skill.trustTier).toBe('community'); // 100 stars >= 50
|
|
306
306
|
expect(skill.tags).toContain('claude-code-skill');
|
|
307
307
|
});
|
|
308
|
-
it('should assign
|
|
308
|
+
it('should assign official tier for official repos', () => {
|
|
309
309
|
const indexer = new GitHubIndexer();
|
|
310
310
|
const repo = {
|
|
311
311
|
owner: 'anthropic',
|
|
@@ -320,7 +320,7 @@ describe('GitHubIndexer', () => {
|
|
|
320
320
|
defaultBranch: 'main',
|
|
321
321
|
};
|
|
322
322
|
const skill = indexer.repositoryToSkill(repo);
|
|
323
|
-
expect(skill.trustTier).toBe('
|
|
323
|
+
expect(skill.trustTier).toBe('official');
|
|
324
324
|
});
|
|
325
325
|
it('should assign experimental tier for low-star repos', () => {
|
|
326
326
|
const indexer = new GitHubIndexer();
|
|
@@ -7,13 +7,16 @@ import { describe, it, expect } from 'vitest';
|
|
|
7
7
|
import { ApiSearchResultSchema, SearchResponseSchema, SingleSkillResponseSchema, TelemetryResponseSchema, TrustTierSchema, } from '../../src/api/client.js';
|
|
8
8
|
describe('API Response Validation Schemas', () => {
|
|
9
9
|
describe('TrustTierSchema', () => {
|
|
10
|
-
it('should accept valid trust tiers', () => {
|
|
10
|
+
it('should accept valid public trust tiers', () => {
|
|
11
|
+
expect(TrustTierSchema.safeParse('official').success).toBe(true);
|
|
11
12
|
expect(TrustTierSchema.safeParse('verified').success).toBe(true);
|
|
13
|
+
expect(TrustTierSchema.safeParse('curated').success).toBe(true);
|
|
12
14
|
expect(TrustTierSchema.safeParse('community').success).toBe(true);
|
|
13
|
-
expect(TrustTierSchema.safeParse('
|
|
14
|
-
expect(TrustTierSchema.safeParse('unknown').success).toBe(true);
|
|
15
|
+
expect(TrustTierSchema.safeParse('unverified').success).toBe(true);
|
|
15
16
|
});
|
|
16
|
-
it('should reject invalid
|
|
17
|
+
it('should reject internal-only and invalid tiers', () => {
|
|
18
|
+
expect(TrustTierSchema.safeParse('experimental').success).toBe(false);
|
|
19
|
+
expect(TrustTierSchema.safeParse('unknown').success).toBe(false);
|
|
17
20
|
expect(TrustTierSchema.safeParse('invalid').success).toBe(false);
|
|
18
21
|
expect(TrustTierSchema.safeParse('').success).toBe(false);
|
|
19
22
|
expect(TrustTierSchema.safeParse(123).success).toBe(false);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.validation.test.js","sourceRoot":"","sources":["../../../tests/api/client.validation.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,yBAAyB,EACzB,uBAAuB,EACvB,eAAe,GAChB,MAAM,yBAAyB,CAAA;AAEhC,QAAQ,CAAC,iCAAiC,EAAE,GAAG,EAAE;IAC/C,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;QAC/B,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"client.validation.test.js","sourceRoot":"","sources":["../../../tests/api/client.validation.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-5039: Unit tests for the canonical `probeEmbeddingCapability` helper in
|
|
3
|
+
* `@skillsmith/core/embeddings/probe`. Mirrors the shape of the legacy
|
|
4
|
+
* `packages/mcp-server/tests/startup-probe.test.ts` unit tier (SMI-5009) but
|
|
5
|
+
* exercises the real exported function — not an inline copy — so the four
|
|
6
|
+
* consumers (mcp-server, doc-retrieval-mcp server/cli, cli) all share the
|
|
7
|
+
* same audited behavior.
|
|
8
|
+
*
|
|
9
|
+
* The mcp-server integration tier (spawn-based) is preserved separately to
|
|
10
|
+
* pin the MCP stdio invariant — see SMI-5009 for the rationale.
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=probe.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"probe.test.d.ts","sourceRoot":"","sources":["../../../tests/embeddings/probe.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-5039: Unit tests for the canonical `probeEmbeddingCapability` helper in
|
|
3
|
+
* `@skillsmith/core/embeddings/probe`. Mirrors the shape of the legacy
|
|
4
|
+
* `packages/mcp-server/tests/startup-probe.test.ts` unit tier (SMI-5009) but
|
|
5
|
+
* exercises the real exported function — not an inline copy — so the four
|
|
6
|
+
* consumers (mcp-server, doc-retrieval-mcp server/cli, cli) all share the
|
|
7
|
+
* same audited behavior.
|
|
8
|
+
*
|
|
9
|
+
* The mcp-server integration tier (spawn-based) is preserved separately to
|
|
10
|
+
* pin the MCP stdio invariant — see SMI-5009 for the rationale.
|
|
11
|
+
*/
|
|
12
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
13
|
+
import { EmbeddingService } from '../../src/embeddings/index.js';
|
|
14
|
+
import { probeEmbeddingCapability } from '../../src/embeddings/probe.js';
|
|
15
|
+
describe('SMI-5039 probeEmbeddingCapability — success path', () => {
|
|
16
|
+
let checkSpy;
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
checkSpy = vi.spyOn(EmbeddingService, 'checkAvailability');
|
|
19
|
+
// Spy on getTransformersLoadError to prevent any accidental real-module
|
|
20
|
+
// access during the test even though the success path never reads it.
|
|
21
|
+
vi.spyOn(EmbeddingService, 'getTransformersLoadError');
|
|
22
|
+
});
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
vi.restoreAllMocks();
|
|
25
|
+
delete process.env['SKILLSMITH_QUIET'];
|
|
26
|
+
});
|
|
27
|
+
it('is silent when real embeddings are available', async () => {
|
|
28
|
+
checkSpy.mockResolvedValue(true);
|
|
29
|
+
const logs = [];
|
|
30
|
+
await probeEmbeddingCapability({ logger: (m) => logs.push(m) });
|
|
31
|
+
expect(logs).toEqual([]);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
describe('SMI-5039 probeEmbeddingCapability — mock fallback path', () => {
|
|
35
|
+
let checkSpy;
|
|
36
|
+
let getErrSpy;
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
checkSpy = vi.spyOn(EmbeddingService, 'checkAvailability');
|
|
39
|
+
getErrSpy = vi.spyOn(EmbeddingService, 'getTransformersLoadError');
|
|
40
|
+
});
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
vi.restoreAllMocks();
|
|
43
|
+
delete process.env['SKILLSMITH_QUIET'];
|
|
44
|
+
});
|
|
45
|
+
it('logs structured mock-fallback warning with reason when checkAvailability returns false', async () => {
|
|
46
|
+
checkSpy.mockResolvedValue(false);
|
|
47
|
+
getErrSpy.mockReturnValue(new Error('ENOENT: cannot find module @huggingface/transformers'));
|
|
48
|
+
const logs = [];
|
|
49
|
+
await probeEmbeddingCapability({ logger: (m) => logs.push(m) });
|
|
50
|
+
expect(logs).toHaveLength(1);
|
|
51
|
+
expect(logs[0]).toMatch(/\[skillsmith\] embeddings: mock \(transformers unavailable: ENOENT: cannot find module @huggingface\/transformers/);
|
|
52
|
+
// Remediation hint MUST be present.
|
|
53
|
+
expect(logs[0]).toContain('install @huggingface/transformers');
|
|
54
|
+
expect(logs[0]).toContain('SKILLSMITH_USE_MOCK_EMBEDDINGS=true');
|
|
55
|
+
});
|
|
56
|
+
it('falls back to "module-load-failed" when no load error is recorded', async () => {
|
|
57
|
+
checkSpy.mockResolvedValue(false);
|
|
58
|
+
getErrSpy.mockReturnValue(null);
|
|
59
|
+
const logs = [];
|
|
60
|
+
await probeEmbeddingCapability({ logger: (m) => logs.push(m) });
|
|
61
|
+
expect(logs).toHaveLength(1);
|
|
62
|
+
expect(logs[0]).toContain('transformers unavailable: module-load-failed');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe('SMI-5039 probeEmbeddingCapability — timeout path', () => {
|
|
66
|
+
let checkSpy;
|
|
67
|
+
let getErrSpy;
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
checkSpy = vi.spyOn(EmbeddingService, 'checkAvailability');
|
|
70
|
+
getErrSpy = vi.spyOn(EmbeddingService, 'getTransformersLoadError');
|
|
71
|
+
});
|
|
72
|
+
afterEach(() => {
|
|
73
|
+
vi.restoreAllMocks();
|
|
74
|
+
delete process.env['SKILLSMITH_QUIET'];
|
|
75
|
+
});
|
|
76
|
+
it('emits probe-timeout line and returns within the bound when checkAvailability hangs', async () => {
|
|
77
|
+
// Hangs forever — only the timeout sentinel should resolve.
|
|
78
|
+
checkSpy.mockImplementation(() => new Promise(() => undefined));
|
|
79
|
+
getErrSpy.mockReturnValue(null);
|
|
80
|
+
const logs = [];
|
|
81
|
+
const start = Date.now();
|
|
82
|
+
await probeEmbeddingCapability({ logger: (m) => logs.push(m), timeoutMs: 100 });
|
|
83
|
+
const elapsed = Date.now() - start;
|
|
84
|
+
expect(logs).toHaveLength(1);
|
|
85
|
+
expect(logs[0]).toMatch(/embeddings: mock \(transformers unavailable: probe-timeout/);
|
|
86
|
+
// Must complete within a small multiple of the timeout — proves the
|
|
87
|
+
// hard-bound holds even when checkAvailability never resolves.
|
|
88
|
+
expect(elapsed).toBeLessThan(2000);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
describe('SMI-5039 probeEmbeddingCapability — error path', () => {
|
|
92
|
+
let checkSpy;
|
|
93
|
+
let getErrSpy;
|
|
94
|
+
beforeEach(() => {
|
|
95
|
+
checkSpy = vi.spyOn(EmbeddingService, 'checkAvailability');
|
|
96
|
+
getErrSpy = vi.spyOn(EmbeddingService, 'getTransformersLoadError');
|
|
97
|
+
});
|
|
98
|
+
afterEach(() => {
|
|
99
|
+
vi.restoreAllMocks();
|
|
100
|
+
delete process.env['SKILLSMITH_QUIET'];
|
|
101
|
+
});
|
|
102
|
+
it('catches a thrown error and logs probe-failed without rethrowing', async () => {
|
|
103
|
+
checkSpy.mockRejectedValue(new Error('boom'));
|
|
104
|
+
getErrSpy.mockReturnValue(null);
|
|
105
|
+
const logs = [];
|
|
106
|
+
await expect(probeEmbeddingCapability({ logger: (m) => logs.push(m) })).resolves.toBeUndefined();
|
|
107
|
+
expect(logs).toHaveLength(1);
|
|
108
|
+
expect(logs[0]).toMatch(/embeddings: probe-failed \(boom/);
|
|
109
|
+
expect(logs[0]).toContain('install @huggingface/transformers');
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
describe('SMI-5039 probeEmbeddingCapability — quiet mode', () => {
|
|
113
|
+
let checkSpy;
|
|
114
|
+
let getErrSpy;
|
|
115
|
+
beforeEach(() => {
|
|
116
|
+
checkSpy = vi.spyOn(EmbeddingService, 'checkAvailability');
|
|
117
|
+
getErrSpy = vi.spyOn(EmbeddingService, 'getTransformersLoadError');
|
|
118
|
+
});
|
|
119
|
+
afterEach(() => {
|
|
120
|
+
vi.restoreAllMocks();
|
|
121
|
+
delete process.env['SKILLSMITH_QUIET'];
|
|
122
|
+
});
|
|
123
|
+
it('suppresses the warning when opts.quiet=true', async () => {
|
|
124
|
+
checkSpy.mockResolvedValue(false);
|
|
125
|
+
getErrSpy.mockReturnValue(new Error('boom'));
|
|
126
|
+
const logs = [];
|
|
127
|
+
await probeEmbeddingCapability({ logger: (m) => logs.push(m), quiet: true });
|
|
128
|
+
expect(logs).toEqual([]);
|
|
129
|
+
});
|
|
130
|
+
it('suppresses the warning when SKILLSMITH_QUIET=true', async () => {
|
|
131
|
+
checkSpy.mockResolvedValue(false);
|
|
132
|
+
getErrSpy.mockReturnValue(new Error('boom'));
|
|
133
|
+
process.env['SKILLSMITH_QUIET'] = 'true';
|
|
134
|
+
const logs = [];
|
|
135
|
+
await probeEmbeddingCapability({ logger: (m) => logs.push(m) });
|
|
136
|
+
expect(logs).toEqual([]);
|
|
137
|
+
});
|
|
138
|
+
it('honors SKILLSMITH_QUIET=1 (numeric truthy)', async () => {
|
|
139
|
+
checkSpy.mockResolvedValue(false);
|
|
140
|
+
getErrSpy.mockReturnValue(new Error('boom'));
|
|
141
|
+
process.env['SKILLSMITH_QUIET'] = '1';
|
|
142
|
+
const logs = [];
|
|
143
|
+
await probeEmbeddingCapability({ logger: (m) => logs.push(m) });
|
|
144
|
+
expect(logs).toEqual([]);
|
|
145
|
+
});
|
|
146
|
+
it('does NOT suppress when SKILLSMITH_QUIET is unset and quiet=false', async () => {
|
|
147
|
+
checkSpy.mockResolvedValue(false);
|
|
148
|
+
getErrSpy.mockReturnValue(new Error('boom'));
|
|
149
|
+
const logs = [];
|
|
150
|
+
await probeEmbeddingCapability({ logger: (m) => logs.push(m), quiet: false });
|
|
151
|
+
expect(logs).toHaveLength(1);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
//# sourceMappingURL=probe.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"probe.test.js","sourceRoot":"","sources":["../../../tests/embeddings/probe.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../tests/fixtures/api-responses/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA0BH,+EAA+E;AAC/E,uBAAuB;AACvB,+EAA+E;AAE/E,MAAM,CAAC,MAAM,UAAU,GAAa;IAClC,EAAE,EAAE,wBAAwB;IAC5B,IAAI,EAAE,YAAY;IAClB,WAAW,EAAE,wCAAwC;IACrD,MAAM,EAAE,aAAa;IACrB,QAAQ,EAAE,2CAA2C;IACrD,aAAa,EAAE,IAAI;IACnB,UAAU,EAAE,WAAW;IACvB,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC;IACpC,KAAK,EAAE,GAAG;IACV,UAAU,EAAE,sBAAsB;IAClC,UAAU,EAAE,sBAAsB;IAClC,UAAU,EAAE,CAAC,aAAa,CAAC;CAC5B,CAAA;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAoB;IACjD,GAAG,UAAU;IACb,IAAI,EAAE,CAAC;CACR,CAAA;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAuB;IAClD,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,CAAC;IACR,KAAK,EAAE,EAAE;IACT,MAAM,EAAE,CAAC;IACT,OAAO,EAAE;QACP,QAAQ,EAAE,IAAI;QACd,UAAU,EAAE,IAAI;QAChB,SAAS,EAAE,IAAI;KAChB;CACF,CAAA;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAiC;IAC/D,MAAM,EAAE,GAAG;IACX,OAAO,EAAE;QACP,cAAc,EAAE,kBAAkB;QAClC,mBAAmB,EAAE,KAAK;QAC1B,uBAAuB,EAAE,IAAI;QAC7B,mBAAmB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;KAChD;IACD,IAAI,EAAE;QACJ,IAAI,EAAE,CAAC,kBAAkB,CAAC;QAC1B,IAAI,EAAE,gBAAgB;KACvB;CACF,CAAA;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAqB;IACtD,GAAG,UAAU;IACb,eAAe,EAAE,IAAI;CACtB,CAAA;AAED,MAAM,CAAC,MAAM,mBAAmB,GAA0B;IACxD,KAAK,EAAE,CAAC,YAAY,EAAE,OAAO,CAAC;IAC9B,YAAY,EAAE,KAAK;IACnB,KAAK,EAAE,CAAC;IACR,KAAK,EAAE,EAAE;CACV,CAAA;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAoC;IACrE,MAAM,EAAE,GAAG;IACX,OAAO,EAAE;QACP,cAAc,EAAE,kBAAkB;KACnC;IACD,IAAI,EAAE;QACJ,IAAI,EAAE,CAAC,sBAAsB,CAAC;QAC9B,IAAI,EAAE,mBAAmB;KAC1B;CACF,CAAA;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAgC;IAC9D,MAAM,EAAE,GAAG;IACX,OAAO,EAAE;QACP,cAAc,EAAE,kBAAkB;KACnC;IACD,IAAI,EAAE;QACJ,IAAI,EAAE,UAAU;KACjB;CACF,CAAA;AAED,MAAM,CAAC,MAAM,mBAAmB,GAA+B;IAC7D,MAAM,EAAE,GAAG;IACX,OAAO,EAAE;QACP,cAAc,EAAE,kBAAkB;KACnC;IACD,IAAI,EAAE;QACJ,MAAM,EAAE,SAAS;QACjB,OAAO,EAAE,OAAO;QAChB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC;CACF,CAAA;AAED,+EAA+E;AAC/E,+BAA+B;AAC/B,+EAA+E;AAE/E;;GAEG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAa;IAC1C,EAAE,EAAE,eAAe;IACnB,IAAI,EAAE,eAAe;IACrB,WAAW,EAAE,IAAI;IACjB,MAAM,EAAE,IAAI;IACZ,QAAQ,EAAE,IAAI;IACd,aAAa,EAAE,IAAI;IACnB,UAAU,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../tests/fixtures/api-responses/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA0BH,+EAA+E;AAC/E,uBAAuB;AACvB,+EAA+E;AAE/E,MAAM,CAAC,MAAM,UAAU,GAAa;IAClC,EAAE,EAAE,wBAAwB;IAC5B,IAAI,EAAE,YAAY;IAClB,WAAW,EAAE,wCAAwC;IACrD,MAAM,EAAE,aAAa;IACrB,QAAQ,EAAE,2CAA2C;IACrD,aAAa,EAAE,IAAI;IACnB,UAAU,EAAE,WAAW;IACvB,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC;IACpC,KAAK,EAAE,GAAG;IACV,UAAU,EAAE,sBAAsB;IAClC,UAAU,EAAE,sBAAsB;IAClC,UAAU,EAAE,CAAC,aAAa,CAAC;CAC5B,CAAA;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAoB;IACjD,GAAG,UAAU;IACb,IAAI,EAAE,CAAC;CACR,CAAA;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAuB;IAClD,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,CAAC;IACR,KAAK,EAAE,EAAE;IACT,MAAM,EAAE,CAAC;IACT,OAAO,EAAE;QACP,QAAQ,EAAE,IAAI;QACd,UAAU,EAAE,IAAI;QAChB,SAAS,EAAE,IAAI;KAChB;CACF,CAAA;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAiC;IAC/D,MAAM,EAAE,GAAG;IACX,OAAO,EAAE;QACP,cAAc,EAAE,kBAAkB;QAClC,mBAAmB,EAAE,KAAK;QAC1B,uBAAuB,EAAE,IAAI;QAC7B,mBAAmB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;KAChD;IACD,IAAI,EAAE;QACJ,IAAI,EAAE,CAAC,kBAAkB,CAAC;QAC1B,IAAI,EAAE,gBAAgB;KACvB;CACF,CAAA;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAqB;IACtD,GAAG,UAAU;IACb,eAAe,EAAE,IAAI;CACtB,CAAA;AAED,MAAM,CAAC,MAAM,mBAAmB,GAA0B;IACxD,KAAK,EAAE,CAAC,YAAY,EAAE,OAAO,CAAC;IAC9B,YAAY,EAAE,KAAK;IACnB,KAAK,EAAE,CAAC;IACR,KAAK,EAAE,EAAE;CACV,CAAA;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAoC;IACrE,MAAM,EAAE,GAAG;IACX,OAAO,EAAE;QACP,cAAc,EAAE,kBAAkB;KACnC;IACD,IAAI,EAAE;QACJ,IAAI,EAAE,CAAC,sBAAsB,CAAC;QAC9B,IAAI,EAAE,mBAAmB;KAC1B;CACF,CAAA;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAgC;IAC9D,MAAM,EAAE,GAAG;IACX,OAAO,EAAE;QACP,cAAc,EAAE,kBAAkB;KACnC;IACD,IAAI,EAAE;QACJ,IAAI,EAAE,UAAU;KACjB;CACF,CAAA;AAED,MAAM,CAAC,MAAM,mBAAmB,GAA+B;IAC7D,MAAM,EAAE,GAAG;IACX,OAAO,EAAE;QACP,cAAc,EAAE,kBAAkB;KACnC;IACD,IAAI,EAAE;QACJ,MAAM,EAAE,SAAS;QACjB,OAAO,EAAE,OAAO;QAChB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC;CACF,CAAA;AAED,+EAA+E;AAC/E,+BAA+B;AAC/B,+EAA+E;AAE/E;;GAEG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAa;IAC1C,EAAE,EAAE,eAAe;IACnB,IAAI,EAAE,eAAe;IACrB,WAAW,EAAE,IAAI;IACjB,MAAM,EAAE,IAAI;IACZ,QAAQ,EAAE,IAAI;IACd,aAAa,EAAE,IAAI;IACnB,UAAU,EAAE,WAAW;IACvB,IAAI,EAAE,EAAE;IACR,KAAK,EAAE,IAAI;IACX,UAAU,EAAE,sBAAsB;IAClC,UAAU,EAAE,sBAAsB;CACnC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAiC;IAC7D,MAAM,EAAE,GAAG;IACX,OAAO,EAAE;QACP,cAAc,EAAE,kBAAkB;KACnC;IACD,IAAI,EAAE;QACJ,IAAI,EAAE,EAAE;QACR,IAAI,EAAE;YACJ,KAAK,EAAE,uBAAuB;YAC9B,KAAK,EAAE,CAAC;YACR,KAAK,EAAE,EAAE;YACT,MAAM,EAAE,CAAC;YACT,OAAO,EAAE;gBACP,QAAQ,EAAE,IAAI;gBACd,UAAU,EAAE,IAAI;gBAChB,SAAS,EAAE,IAAI;aAChB;SACF;KACF;CACF,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAiC;IAC/D,MAAM,EAAE,GAAG;IACX,OAAO,EAAE;QACP,cAAc,EAAE,kBAAkB;KACnC;IACD,IAAI,EAAE;QACJ,IAAI,EAAE,CAAC,EAAE,GAAG,kBAAkB,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QAC1C,IAAI,EAAE;YACJ,KAAK,EAAE,SAAS;YAChB,KAAK,EAAE,CAAC;YACR,KAAK,EAAE,EAAE;YACT,MAAM,EAAE,CAAC;YACT,OAAO,EAAE;gBACP,QAAQ,EAAE,IAAI;gBACd,UAAU,EAAE,IAAI;gBAChB,SAAS,EAAE,IAAI;aAChB;SACF;KACF;CACF,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAoC;IACnE,MAAM,EAAE,GAAG;IACX,OAAO,EAAE;QACP,cAAc,EAAE,kBAAkB;KACnC;IACD,IAAI,EAAE;QACJ,IAAI,EAAE,EAAE;QACR,IAAI,EAAE;YACJ,KAAK,EAAE,CAAC,cAAc,CAAC;YACvB,YAAY,EAAE,IAAI;YAClB,KAAK,EAAE,CAAC;YACR,KAAK,EAAE,EAAE;SACV;KACF;CACF,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAiC;IACjE,MAAM,EAAE,GAAG;IACX,OAAO,EAAE;QACP,cAAc,EAAE,kBAAkB;QAClC,IAAI,EAAE,2LAA2L;KAClM;IACD,IAAI,EAAE;QACJ,IAAI,EAAE,CAAC,kBAAkB,CAAC;QAC1B,IAAI,EAAE;YACJ,KAAK,EAAE,aAAa;YACpB,KAAK,EAAE,EAAE;YACT,KAAK,EAAE,EAAE;YACT,MAAM,EAAE,EAAE;YACV,OAAO,EAAE;gBACP,QAAQ,EAAE,IAAI;gBACd,UAAU,EAAE,WAAW;gBACvB,SAAS,EAAE,IAAI;aAChB;SACF;KACF;CACF,CAAA;AAED,+EAA+E;AAC/E,kBAAkB;AAClB,+EAA+E;AAE/E,MAAM,CAAC,MAAM,oBAAoB,GAAmC;IAClE,MAAM,EAAE,GAAG;IACX,OAAO,EAAE;QACP,cAAc,EAAE,kBAAkB;KACnC;IACD,IAAI,EAAE;QACJ,KAAK,EAAE,iBAAiB;QACxB,OAAO,EAAE;YACP,QAAQ,EAAE,mBAAmB;SAC9B;KACF;CACF,CAAA;AAED,MAAM,CAAC,MAAM,qBAAqB,GAAmC;IACnE,MAAM,EAAE,GAAG;IACX,OAAO,EAAE;QACP,cAAc,EAAE,kBAAkB;KACnC;IACD,IAAI,EAAE;QACJ,KAAK,EAAE,qCAAqC;QAC5C,OAAO,EAAE;YACP,KAAK,EAAE,OAAO;YACd,QAAQ,EAAE,GAAG;SACd;KACF;CACF,CAAA;AAED,MAAM,CAAC,MAAM,uBAAuB,GAAmC;IACrE,MAAM,EAAE,GAAG;IACX,OAAO,EAAE;QACP,cAAc,EAAE,kBAAkB;KACnC;IACD,IAAI,EAAE;QACJ,KAAK,EAAE,4BAA4B;KACpC;CACF,CAAA;AAED,MAAM,CAAC,MAAM,oBAAoB,GAAmC;IAClE,MAAM,EAAE,GAAG;IACX,OAAO,EAAE;QACP,cAAc,EAAE,kBAAkB;KACnC;IACD,IAAI,EAAE;QACJ,KAAK,EAAE,6CAA6C;QACpD,OAAO,EAAE;YACP,aAAa,EAAE,MAAM;YACrB,YAAY,EAAE,WAAW;SAC1B;KACF;CACF,CAAA;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAmC;IAC/D,MAAM,EAAE,GAAG;IACX,OAAO,EAAE;QACP,cAAc,EAAE,kBAAkB;KACnC;IACD,IAAI,EAAE;QACJ,KAAK,EAAE,8BAA8B;KACtC;CACF,CAAA;AAED,MAAM,CAAC,MAAM,8BAA8B,GAAmC;IAC5E,MAAM,EAAE,GAAG;IACX,OAAO,EAAE;QACP,cAAc,EAAE,kBAAkB;QAClC,aAAa,EAAE,IAAI;KACpB;IACD,IAAI,EAAE;QACJ,KAAK,EAAE,iCAAiC;QACxC,OAAO,EAAE;YACP,WAAW,EAAE,EAAE;SAChB;KACF;CACF,CAAA;AAED,+EAA+E;AAC/E,0BAA0B;AAC1B,+EAA+E;AAE/E,MAAM,CAAC,MAAM,oBAAoB,GAAkB;IACjD,KAAK,EAAE,GAAG;IACV,SAAS,EAAE,CAAC;IACZ,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;CAC1B,CAAA;AAED,MAAM,CAAC,MAAM,uBAAuB,GAAmC;IACrE,MAAM,EAAE,GAAG;IACX,OAAO,EAAE;QACP,cAAc,EAAE,kBAAkB;QAClC,mBAAmB,EAAE,KAAK;QAC1B,uBAAuB,EAAE,GAAG;QAC5B,mBAAmB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;QAC/C,aAAa,EAAE,IAAI;KACpB;IACD,IAAI,EAAE;QACJ,KAAK,EAAE,mBAAmB;QAC1B,OAAO,EAAE;YACP,KAAK,EAAE,GAAG;YACV,SAAS,EAAE,CAAC;YACZ,QAAQ,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC,WAAW,EAAE;SACrD;KACF;CACF,CAAA;AAED,MAAM,CAAC,MAAM,uBAAuB,GAAiC;IACnE,MAAM,EAAE,GAAG;IACX,OAAO,EAAE;QACP,cAAc,EAAE,kBAAkB;QAClC,mBAAmB,EAAE,KAAK;QAC1B,uBAAuB,EAAE,GAAG;QAC5B,mBAAmB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;KAChD;IACD,IAAI,EAAE,mBAAmB,CAAC,IAAI;CAC/B,CAAA;AAED,+EAA+E;AAC/E,4BAA4B;AAC5B,+EAA+E;AAE/E;;GAEG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,IAAI,CAAA;AAE/C;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,kCAAkC,CAAA;AAEnE;;GAEG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,+BAA+B,CAAA;AAElE;;GAEG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG;;;;;;CAMvC,CAAA;AAED,+EAA+E;AAC/E,mBAAmB;AACnB,+EAA+E;AAE/E;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAI,IAAqB;IACzD,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAElF,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE;QACxB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,OAAO,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;KACnC,CAAC,CAAA;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAI,IAAqB;IACtD,OAAO,KAAK,IAAI,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAA;AAC7C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAO,GAAG,eAAe;IAC/D,OAAO,KAAK,IAAI,EAAE;QAChB,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAA;IAC1B,CAAC,CAAA;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAO,GAAG,2BAA2B;IACtE,OAAO,KAAK,IAAI,EAAE;QAChB,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;QAC5D,OAAO,kBAAkB,CAAC,mBAAmB,CAAC,CAAA;IAChD,CAAC,CAAA;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB,CAAC,SAAkC;IACtE,IAAI,SAAS,GAAG,CAAC,CAAA;IACjB,OAAO,KAAK,IAAI,EAAE;QAChB,MAAM,QAAQ,GAAG,SAAS,CAAC,SAAS,CAAC,IAAI,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;QACxE,SAAS,EAAE,CAAA;QACX,OAAO,kBAAkB,CAAC,QAAQ,CAAC,CAAA;IACrC,CAAC,CAAA;AACH,CAAC;AAED,+EAA+E;AAC/E,sCAAsC;AACtC,+EAA+E;AAE/E,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,UAAU;IACV,aAAa,EAAE,mBAAmB;IAClC,gBAAgB,EAAE,sBAAsB;IACxC,aAAa,EAAE,mBAAmB;IAClC,aAAa,EAAE,mBAAmB;IAElC,gBAAgB;IAChB,WAAW,EAAE,iBAAiB;IAC9B,aAAa,EAAE,mBAAmB;IAClC,eAAe,EAAE,qBAAqB;IACtC,cAAc,EAAE,oBAAoB;IACpC,YAAY,EAAE,kBAAkB;IAEhC,SAAS;IACT,aAAa,EAAE,oBAAoB;IACnC,eAAe,EAAE,qBAAqB;IACtC,iBAAiB,EAAE,uBAAuB;IAC1C,cAAc,EAAE,oBAAoB;IACpC,WAAW,EAAE,iBAAiB;IAC9B,uBAAuB,EAAE,8BAA8B;IACvD,gBAAgB,EAAE,uBAAuB;IAEzC,gBAAgB;IAChB,gBAAgB,EAAE,uBAAuB;IACzC,aAAa,EAAE,oBAAoB;IAEnC,aAAa;IACb,WAAW,EAAE,iBAAiB;IAC9B,aAAa,EAAE,mBAAmB;IAClC,SAAS,EAAE,wBAAwB;CACpC,CAAA;AAED,eAAe,SAAS,CAAA"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-5358: QuarantineService Concurrency / Race Tests
|
|
3
|
+
*
|
|
4
|
+
* Existing coverage (QuarantineService.test.ts) only exercises single, strictly
|
|
5
|
+
* sequential reviews. These tests model OVERLAPPING reviewer/writer operations
|
|
6
|
+
* against the SAME quarantine entry and assert the concurrency invariants that
|
|
7
|
+
* protect against lost updates and illegal state transitions:
|
|
8
|
+
*
|
|
9
|
+
* 1. Two reviewers racing to decide one SUSPICIOUS entry (approve vs reject)
|
|
10
|
+
* -> exactly one wins, the loser is rejected as ALREADY_REVIEWED, and the
|
|
11
|
+
* persisted DB state reflects the winner's decision (no lost update).
|
|
12
|
+
* 2. A stale-snapshot ("optimistic version") writer whose read predates another
|
|
13
|
+
* reviewer's decision -> the stale write is rejected, persisted state is
|
|
14
|
+
* unchanged from the winner.
|
|
15
|
+
* 3. MALICIOUS double-approval by the SAME reviewer -> the second is rejected,
|
|
16
|
+
* only one approval row exists, and the entry does NOT illegally transition.
|
|
17
|
+
* 4. MALICIOUS approval by two DISTINCT reviewers reaching the required count
|
|
18
|
+
* -> the entry transitions to approved EXACTLY once with exactly two
|
|
19
|
+
* approval rows (no double-count, no double-transition).
|
|
20
|
+
*
|
|
21
|
+
* HONESTY NOTE: better-sqlite3 is fully SYNCHRONOUS, so genuine OS-thread
|
|
22
|
+
* interleaving is not achievable in-process. `Promise.all` / `Promise.allSettled`
|
|
23
|
+
* here schedule each synchronous `service.review()` as a microtask; each call
|
|
24
|
+
* therefore runs to completion atomically rather than truly interleaving. We are
|
|
25
|
+
* NOT faking parallelism. What we test is the REAL concurrency invariant: the
|
|
26
|
+
* read-then-act guard (`reviewStatus !== 'pending'`), the per-reviewer guard
|
|
27
|
+
* (`hasReviewerApproved`), and the approval-count check are what prevent lost
|
|
28
|
+
* updates / illegal transitions when overlapping operations target the same
|
|
29
|
+
* entry. All assertions read the PERSISTED DB state after the race, never a mock
|
|
30
|
+
* return value. Removing any of those guards makes the persisted state
|
|
31
|
+
* inconsistent and fails these tests.
|
|
32
|
+
*/
|
|
33
|
+
export {};
|
|
34
|
+
//# sourceMappingURL=QuarantineService.concurrency.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"QuarantineService.concurrency.test.d.ts","sourceRoot":"","sources":["../../../tests/integration/QuarantineService.concurrency.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG"}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-5358: QuarantineService Concurrency / Race Tests
|
|
3
|
+
*
|
|
4
|
+
* Existing coverage (QuarantineService.test.ts) only exercises single, strictly
|
|
5
|
+
* sequential reviews. These tests model OVERLAPPING reviewer/writer operations
|
|
6
|
+
* against the SAME quarantine entry and assert the concurrency invariants that
|
|
7
|
+
* protect against lost updates and illegal state transitions:
|
|
8
|
+
*
|
|
9
|
+
* 1. Two reviewers racing to decide one SUSPICIOUS entry (approve vs reject)
|
|
10
|
+
* -> exactly one wins, the loser is rejected as ALREADY_REVIEWED, and the
|
|
11
|
+
* persisted DB state reflects the winner's decision (no lost update).
|
|
12
|
+
* 2. A stale-snapshot ("optimistic version") writer whose read predates another
|
|
13
|
+
* reviewer's decision -> the stale write is rejected, persisted state is
|
|
14
|
+
* unchanged from the winner.
|
|
15
|
+
* 3. MALICIOUS double-approval by the SAME reviewer -> the second is rejected,
|
|
16
|
+
* only one approval row exists, and the entry does NOT illegally transition.
|
|
17
|
+
* 4. MALICIOUS approval by two DISTINCT reviewers reaching the required count
|
|
18
|
+
* -> the entry transitions to approved EXACTLY once with exactly two
|
|
19
|
+
* approval rows (no double-count, no double-transition).
|
|
20
|
+
*
|
|
21
|
+
* HONESTY NOTE: better-sqlite3 is fully SYNCHRONOUS, so genuine OS-thread
|
|
22
|
+
* interleaving is not achievable in-process. `Promise.all` / `Promise.allSettled`
|
|
23
|
+
* here schedule each synchronous `service.review()` as a microtask; each call
|
|
24
|
+
* therefore runs to completion atomically rather than truly interleaving. We are
|
|
25
|
+
* NOT faking parallelism. What we test is the REAL concurrency invariant: the
|
|
26
|
+
* read-then-act guard (`reviewStatus !== 'pending'`), the per-reviewer guard
|
|
27
|
+
* (`hasReviewerApproved`), and the approval-count check are what prevent lost
|
|
28
|
+
* updates / illegal transitions when overlapping operations target the same
|
|
29
|
+
* entry. All assertions read the PERSISTED DB state after the race, never a mock
|
|
30
|
+
* return value. Removing any of those guards makes the persisted state
|
|
31
|
+
* inconsistent and fails these tests.
|
|
32
|
+
*/
|
|
33
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
34
|
+
import { QuarantineService } from '../../src/services/quarantine/QuarantineService.js';
|
|
35
|
+
import { QuarantineServiceError } from '../../src/services/quarantine/types.js';
|
|
36
|
+
import { QuarantineRepository } from '../../src/repositories/quarantine/index.js';
|
|
37
|
+
import { ApprovalRepository } from '../../src/repositories/quarantine/index.js';
|
|
38
|
+
import { AuditLogger } from '../../src/security/AuditLogger.js';
|
|
39
|
+
import { createDatabaseSync } from '../../src/db/createDatabase.js';
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// Test Helpers
|
|
42
|
+
// ============================================================================
|
|
43
|
+
const REVIEW_PERMS = ['quarantine:read', 'quarantine:review'];
|
|
44
|
+
const MALICIOUS_PERMS = [
|
|
45
|
+
'quarantine:read',
|
|
46
|
+
'quarantine:review',
|
|
47
|
+
'quarantine:review_malicious',
|
|
48
|
+
];
|
|
49
|
+
function makeSession(overrides = {}) {
|
|
50
|
+
return {
|
|
51
|
+
userId: 'user-123',
|
|
52
|
+
email: 'reviewer@example.com',
|
|
53
|
+
displayName: 'Test Reviewer',
|
|
54
|
+
permissions: REVIEW_PERMS,
|
|
55
|
+
sessionId: 'session-456',
|
|
56
|
+
expiresAt: new Date(Date.now() + 3600000),
|
|
57
|
+
...overrides,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** Run `fn` on a fresh microtask so overlapping calls are scheduled together. */
|
|
61
|
+
function defer(fn) {
|
|
62
|
+
return Promise.resolve().then(fn);
|
|
63
|
+
}
|
|
64
|
+
function fulfilled(results) {
|
|
65
|
+
return results.filter((r) => r.status === 'fulfilled');
|
|
66
|
+
}
|
|
67
|
+
function rejectedWith(results) {
|
|
68
|
+
return results.filter((r) => r.status === 'rejected');
|
|
69
|
+
}
|
|
70
|
+
// ============================================================================
|
|
71
|
+
// Test Suite
|
|
72
|
+
// ============================================================================
|
|
73
|
+
describe('SMI-5358: QuarantineService concurrency / race invariants', () => {
|
|
74
|
+
let db;
|
|
75
|
+
let repository;
|
|
76
|
+
let approvalRepository;
|
|
77
|
+
let auditLogger;
|
|
78
|
+
let service;
|
|
79
|
+
beforeEach(() => {
|
|
80
|
+
db = createDatabaseSync(':memory:');
|
|
81
|
+
auditLogger = new AuditLogger(db);
|
|
82
|
+
repository = new QuarantineRepository(db, auditLogger);
|
|
83
|
+
approvalRepository = new ApprovalRepository(db);
|
|
84
|
+
service = new QuarantineService(repository, approvalRepository, auditLogger);
|
|
85
|
+
});
|
|
86
|
+
afterEach(() => {
|
|
87
|
+
db.close();
|
|
88
|
+
});
|
|
89
|
+
// ==========================================================================
|
|
90
|
+
// SUSPICIOUS: approve vs reject race on the same entry
|
|
91
|
+
// ==========================================================================
|
|
92
|
+
describe('SUSPICIOUS approve-vs-reject race', () => {
|
|
93
|
+
it('lets exactly one reviewer win; the loser is rejected ALREADY_REVIEWED and no update is lost', async () => {
|
|
94
|
+
const approver = makeSession({ userId: 'user-approve', email: 'approve@example.com' });
|
|
95
|
+
const rejecter = makeSession({ userId: 'user-reject', email: 'reject@example.com' });
|
|
96
|
+
const entry = repository.create({
|
|
97
|
+
skillId: 'test/race-skill',
|
|
98
|
+
source: 'test',
|
|
99
|
+
quarantineReason: 'Overlapping reviews',
|
|
100
|
+
severity: 'SUSPICIOUS',
|
|
101
|
+
});
|
|
102
|
+
const results = await Promise.allSettled([
|
|
103
|
+
defer(() => service.review(approver, entry.id, {
|
|
104
|
+
reviewStatus: 'approved',
|
|
105
|
+
reviewNotes: 'approve path',
|
|
106
|
+
})),
|
|
107
|
+
defer(() => service.review(rejecter, entry.id, {
|
|
108
|
+
reviewStatus: 'rejected',
|
|
109
|
+
reviewNotes: 'reject path',
|
|
110
|
+
})),
|
|
111
|
+
]);
|
|
112
|
+
const winners = fulfilled(results);
|
|
113
|
+
const losers = rejectedWith(results);
|
|
114
|
+
// Exactly one decision succeeds, exactly one is blocked.
|
|
115
|
+
expect(winners.length).toBe(1);
|
|
116
|
+
expect(losers.length).toBe(1);
|
|
117
|
+
expect(losers[0].reason).toBeInstanceOf(QuarantineServiceError);
|
|
118
|
+
expect(losers[0].reason.code).toBe('ALREADY_REVIEWED');
|
|
119
|
+
// Persisted state reflects the WINNER, not a half-written / overwritten row.
|
|
120
|
+
const persisted = repository.findById(entry.id);
|
|
121
|
+
expect(persisted).not.toBeNull();
|
|
122
|
+
expect(persisted.reviewStatus).not.toBe('pending');
|
|
123
|
+
const winner = winners[0].value;
|
|
124
|
+
if (winner.approved) {
|
|
125
|
+
expect(persisted.reviewStatus).toBe('approved');
|
|
126
|
+
expect(persisted.reviewedBy).toBe(approver.email);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
expect(persisted.reviewStatus).toBe('rejected');
|
|
130
|
+
expect(persisted.reviewedBy).toBe(rejecter.email);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
it('rejects a stale-snapshot writer whose read predates the winning decision (no lost update)', () => {
|
|
134
|
+
const winner = makeSession({ userId: 'user-winner', email: 'winner@example.com' });
|
|
135
|
+
const staleWriter = makeSession({ userId: 'user-stale', email: 'stale@example.com' });
|
|
136
|
+
const entry = repository.create({
|
|
137
|
+
skillId: 'test/stale-skill',
|
|
138
|
+
source: 'test',
|
|
139
|
+
quarantineReason: 'Stale snapshot write',
|
|
140
|
+
severity: 'SUSPICIOUS',
|
|
141
|
+
});
|
|
142
|
+
// staleWriter captures a snapshot while the entry is still pending.
|
|
143
|
+
const snapshot = service.findById(staleWriter, entry.id);
|
|
144
|
+
expect(snapshot).not.toBeNull();
|
|
145
|
+
expect(snapshot.reviewStatus).toBe('pending');
|
|
146
|
+
// winner commits a decision first.
|
|
147
|
+
service.review(winner, entry.id, {
|
|
148
|
+
reviewStatus: 'approved',
|
|
149
|
+
reviewNotes: 'committed first',
|
|
150
|
+
});
|
|
151
|
+
// staleWriter now tries to write based on its stale (pending) view.
|
|
152
|
+
expect(() => service.review(staleWriter, entry.id, {
|
|
153
|
+
reviewStatus: 'rejected',
|
|
154
|
+
reviewNotes: 'based on stale read',
|
|
155
|
+
})).toThrow(QuarantineServiceError);
|
|
156
|
+
// The winner's decision survives untouched: the stale reject did not overwrite it.
|
|
157
|
+
const persisted = repository.findById(entry.id);
|
|
158
|
+
expect(persisted.reviewStatus).toBe('approved');
|
|
159
|
+
expect(persisted.reviewedBy).toBe(winner.email);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
// ==========================================================================
|
|
163
|
+
// MALICIOUS: same-reviewer double-approval (per-reviewer guard)
|
|
164
|
+
// ==========================================================================
|
|
165
|
+
describe('MALICIOUS same-reviewer double-approval', () => {
|
|
166
|
+
it('does not double-count or illegally transition when one reviewer approves twice', async () => {
|
|
167
|
+
const reviewer = makeSession({
|
|
168
|
+
userId: 'user-dup',
|
|
169
|
+
email: 'dup@example.com',
|
|
170
|
+
permissions: MALICIOUS_PERMS,
|
|
171
|
+
});
|
|
172
|
+
const entry = repository.create({
|
|
173
|
+
skillId: 'test/malicious-dup',
|
|
174
|
+
source: 'test',
|
|
175
|
+
quarantineReason: 'Malicious code detected',
|
|
176
|
+
severity: 'MALICIOUS',
|
|
177
|
+
});
|
|
178
|
+
const results = await Promise.allSettled([
|
|
179
|
+
defer(() => service.review(reviewer, entry.id, {
|
|
180
|
+
reviewStatus: 'approved',
|
|
181
|
+
reviewNotes: 'first',
|
|
182
|
+
})),
|
|
183
|
+
defer(() => service.review(reviewer, entry.id, {
|
|
184
|
+
reviewStatus: 'approved',
|
|
185
|
+
reviewNotes: 'second (duplicate)',
|
|
186
|
+
})),
|
|
187
|
+
]);
|
|
188
|
+
const winners = fulfilled(results);
|
|
189
|
+
const losers = rejectedWith(results);
|
|
190
|
+
// One approval recorded, the duplicate rejected.
|
|
191
|
+
expect(winners.length).toBe(1);
|
|
192
|
+
expect(losers.length).toBe(1);
|
|
193
|
+
expect(losers[0].reason.code).toBe('ALREADY_REVIEWED');
|
|
194
|
+
// The single accepted approval did NOT complete the workflow.
|
|
195
|
+
expect(winners[0].value.approved).toBe(false);
|
|
196
|
+
// Persisted: exactly ONE approval row, and the entry stays pending.
|
|
197
|
+
const pending = approvalRepository.getPendingApprovals(entry.id);
|
|
198
|
+
expect(pending.length).toBe(1);
|
|
199
|
+
expect(pending[0].reviewerId).toBe('user-dup');
|
|
200
|
+
const persisted = repository.findById(entry.id);
|
|
201
|
+
expect(persisted.reviewStatus).toBe('pending');
|
|
202
|
+
expect(persisted.reviewedBy).toBeNull();
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
// ==========================================================================
|
|
206
|
+
// MALICIOUS: two distinct reviewers reaching the required count
|
|
207
|
+
// ==========================================================================
|
|
208
|
+
describe('MALICIOUS two-reviewer approval completion', () => {
|
|
209
|
+
it('transitions to approved exactly once with exactly two approval rows (no double-count)', async () => {
|
|
210
|
+
const reviewerA = makeSession({
|
|
211
|
+
userId: 'user-A',
|
|
212
|
+
email: 'a@example.com',
|
|
213
|
+
permissions: MALICIOUS_PERMS,
|
|
214
|
+
});
|
|
215
|
+
const reviewerB = makeSession({
|
|
216
|
+
userId: 'user-B',
|
|
217
|
+
email: 'b@example.com',
|
|
218
|
+
permissions: MALICIOUS_PERMS,
|
|
219
|
+
});
|
|
220
|
+
const entry = repository.create({
|
|
221
|
+
skillId: 'test/malicious-two',
|
|
222
|
+
source: 'test',
|
|
223
|
+
quarantineReason: 'Malicious code detected',
|
|
224
|
+
severity: 'MALICIOUS',
|
|
225
|
+
});
|
|
226
|
+
const results = await Promise.allSettled([
|
|
227
|
+
defer(() => service.review(reviewerA, entry.id, {
|
|
228
|
+
reviewStatus: 'approved',
|
|
229
|
+
reviewNotes: 'approval A',
|
|
230
|
+
})),
|
|
231
|
+
defer(() => service.review(reviewerB, entry.id, {
|
|
232
|
+
reviewStatus: 'approved',
|
|
233
|
+
reviewNotes: 'approval B',
|
|
234
|
+
})),
|
|
235
|
+
]);
|
|
236
|
+
const winners = fulfilled(results);
|
|
237
|
+
// Both distinct reviewers are accepted.
|
|
238
|
+
expect(winners.length).toBe(2);
|
|
239
|
+
// Exactly ONE result reports completion (approved), the other in-progress.
|
|
240
|
+
const completed = winners.filter((r) => r.value.approved === true);
|
|
241
|
+
const inProgress = winners.filter((r) => r.value.approved === false);
|
|
242
|
+
expect(completed.length).toBe(1);
|
|
243
|
+
expect(inProgress.length).toBe(1);
|
|
244
|
+
// Persisted: exactly two approval rows, both approvals, all marked complete.
|
|
245
|
+
const allApprovals = approvalRepository.getApprovals(entry.id);
|
|
246
|
+
expect(allApprovals.length).toBe(2);
|
|
247
|
+
expect(allApprovals.filter((a) => a.decision === 'approved').length).toBe(2);
|
|
248
|
+
expect(allApprovals.every((a) => a.isComplete)).toBe(true);
|
|
249
|
+
const reviewerIds = allApprovals.map((a) => a.reviewerId).sort();
|
|
250
|
+
expect(reviewerIds).toEqual(['user-A', 'user-B']);
|
|
251
|
+
// No pending approvals remain, and the entry transitioned to approved once.
|
|
252
|
+
expect(approvalRepository.getPendingApprovals(entry.id).length).toBe(0);
|
|
253
|
+
const persisted = repository.findById(entry.id);
|
|
254
|
+
expect(persisted.reviewStatus).toBe('approved');
|
|
255
|
+
});
|
|
256
|
+
it('keeps the entry pending after a single approval (count guard holds)', () => {
|
|
257
|
+
const reviewerA = makeSession({
|
|
258
|
+
userId: 'user-solo',
|
|
259
|
+
email: 'solo@example.com',
|
|
260
|
+
permissions: MALICIOUS_PERMS,
|
|
261
|
+
});
|
|
262
|
+
const entry = repository.create({
|
|
263
|
+
skillId: 'test/malicious-solo',
|
|
264
|
+
source: 'test',
|
|
265
|
+
quarantineReason: 'Malicious code detected',
|
|
266
|
+
severity: 'MALICIOUS',
|
|
267
|
+
});
|
|
268
|
+
const result = service.review(reviewerA, entry.id, {
|
|
269
|
+
reviewStatus: 'approved',
|
|
270
|
+
reviewNotes: 'only approval',
|
|
271
|
+
});
|
|
272
|
+
// A single approval must NOT unilaterally unquarantine a MALICIOUS skill.
|
|
273
|
+
expect(result.approved).toBe(false);
|
|
274
|
+
const persisted = repository.findById(entry.id);
|
|
275
|
+
expect(persisted.reviewStatus).toBe('pending');
|
|
276
|
+
expect(approvalRepository.getPendingApprovals(entry.id).length).toBe(1);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
//# sourceMappingURL=QuarantineService.concurrency.test.js.map
|