diff --git a/src/eol/utils.test.ts b/src/eol/utils.test.ts index e6aad79..1ae75ef 100644 --- a/src/eol/utils.test.ts +++ b/src/eol/utils.test.ts @@ -6,6 +6,7 @@ import { createPurlIdentity, canonicalizeVersionFilter, } from './utils.ts'; +import { isUnknownReason, UNKNOWN_REASONS } from '../types/eol-scan.ts'; import type { EolScanComponentMetadata } from '../types/eol-scan.ts'; // These are required for the object but not used to derive the status @@ -84,6 +85,42 @@ describe('deriveComponentStatus', () => { const result = deriveComponentStatus(metadata); assert.equal(result, 'OK'); }); + + for (const unknownReason of UNKNOWN_REASONS) { + test(`should return UNKNOWN for unknownReason "${unknownReason}"`, () => { + const result = deriveComponentStatus({ unknownReason }); + assert.equal(result, 'UNKNOWN'); + }); + } + + test('should return UNKNOWN for an unrecognized unknownReason value', () => { + const result = deriveComponentStatus({ + unknownReason: 'something_else', + } as never); + assert.equal(result, 'UNKNOWN'); + }); +}); + +describe('isUnknownReason', () => { + for (const unknownReason of UNKNOWN_REASONS) { + test(`returns true for "${unknownReason}"`, () => { + assert.equal(isUnknownReason(unknownReason), true); + }); + } + + test('returns false for null', () => { + assert.equal(isUnknownReason(null), false); + }); + + test('returns false for an unrecognized string', () => { + assert.equal(isUnknownReason('something_else'), false); + }); + + test('returns false for non-string garbage', () => { + assert.equal(isUnknownReason(42), false); + assert.equal(isUnknownReason(undefined), false); + assert.equal(isUnknownReason({}), false); + }); }); describe('createPurlIdentity', () => { diff --git a/src/eol/utils.ts b/src/eol/utils.ts index aedd571..da27eab 100644 --- a/src/eol/utils.ts +++ b/src/eol/utils.ts @@ -1,8 +1,9 @@ import { PackageURL } from 'packageurl-js'; import type { CdxBom } from '../types/index.js'; import type { + ComponentMetadata, ComponentStatus, - EolScanComponentMetadata, + UnknownComponentMetadata, } from '../types/eol-scan.js'; // Maps non-canonical PURL type tokens to their canonical equivalents. @@ -243,10 +244,16 @@ export function canonicalizeVersionFilter< } as TFilter; } +function isUnknownComponentMetadata( + metadata: ComponentMetadata, +): metadata is UnknownComponentMetadata { + return 'unknownReason' in metadata && metadata.unknownReason != null; +} + export function deriveComponentStatus( - metadata: EolScanComponentMetadata | null, + metadata: ComponentMetadata | null, ): ComponentStatus { - if (!metadata) { + if (!metadata || isUnknownComponentMetadata(metadata)) { return 'UNKNOWN'; } diff --git a/src/index.ts b/src/index.ts index a7f0557..aa24d41 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,6 +16,7 @@ export type { } from './eol/utils.js'; export type * from './types/eol-scan.js'; +export { UNKNOWN_REASONS, isUnknownReason } from './types/eol-scan.js'; export type * from './types/index.js'; export { ComponentScope } from './types/index.js'; diff --git a/src/types/eol-scan.ts b/src/types/eol-scan.ts index c3144a1..e514f2f 100644 --- a/src/types/eol-scan.ts +++ b/src/types/eol-scan.ts @@ -25,6 +25,28 @@ export interface EolScanNextSupportedVersion { releasedAt: Date; } +export const UNKNOWN_REASONS = [ + 'not_identifiable', + 'no_listed_versions', + 'unsupported_ecosystem', + 'queued', +] as const; +export type UnknownReason = (typeof UNKNOWN_REASONS)[number]; + +export interface UnknownComponentMetadata { + unknownReason: UnknownReason; +} + +export type ComponentMetadata = + | EolScanComponentMetadata + | UnknownComponentMetadata; + +export function isUnknownReason(v: unknown): v is UnknownReason { + return ( + typeof v === 'string' && (UNKNOWN_REASONS as readonly string[]).includes(v) + ); +} + export interface NesRemediation { remediations: { purls: { nes: string; oss: string }; @@ -67,7 +89,7 @@ export interface Remediation { } export interface EolScanComponent { - metadata: EolScanComponentMetadata | null; + metadata: ComponentMetadata | null; purl: string; nesRemediation?: NesRemediation | null; remediations?: Remediation[] | null;