From 109c123f05b043fc491b32b4c553a4e3a3f6896c Mon Sep 17 00:00:00 2001 From: James Mountifield Date: Wed, 29 Apr 2026 14:25:22 +0100 Subject: [PATCH 1/2] test(core,testing): verify multi-class NHI entity validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pin the SDK's existing multi-class behavior with NHI as a secondary class. Covers AIASM-14 BDD scenarios: - 14.1/14.2 — User+NHI and AccessKey+NHI multi-class entities created via createIntegrationEntity preserve both primary-class properties (username, fingerprint, etc.) and NHI metadata (_nhiType, _isAi, …). - 14.3 — createMultiClassEntityMetadata accepts an NHI typebox alongside typeboxClassSchemaMap entries, produces a JSON schema with $refs to both classes, and the resulting createEntity output validates cleanly. - 14.4 — toMatchDataModelSchema passes for a User+NHI entity when the NHI schema is registered with the validator singleton. - 14.5 — schemaWhitelistedPropertyNames unions properties across all classes in the array, including the NHI metadata set, for the five combinations called out in the implementation notes (User, AccessKey, Application, Certificate, Secret + NHI). NHI is registered inline in each test file because @jupiterone/data-model 0.62.0 does not yet ship NHI; publishing the schema is AIASM-15. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../__tests__/createIntegrationEntity.test.ts | 263 ++++++++++++++++++ .../createIntegrationHelpers.test.ts | 127 +++++++++ .../src/__tests__/jest.test.ts | 111 ++++++++ 3 files changed, 501 insertions(+) diff --git a/packages/integration-sdk-core/src/data/__tests__/createIntegrationEntity.test.ts b/packages/integration-sdk-core/src/data/__tests__/createIntegrationEntity.test.ts index 7cab0df7e..f91e0b36f 100644 --- a/packages/integration-sdk-core/src/data/__tests__/createIntegrationEntity.test.ts +++ b/packages/integration-sdk-core/src/data/__tests__/createIntegrationEntity.test.ts @@ -1,5 +1,7 @@ import { randomUUID as uuid } from 'crypto'; +import * as dataModel from '@jupiterone/data-model'; + import { createIntegrationEntity, IntegrationEntityData, @@ -7,6 +9,56 @@ import { schemaWhitelists, } from '../createIntegrationEntity'; +// Inline NHI class schema. The published @jupiterone/data-model package does +// not yet ship NHI (AIASM-15 will publish it). For verification we register an +// equivalent of data-model/packages/jupiterone-data-model/src/class_schemas/NHI.json +// so getSchema('NHI') resolves during these tests. +const NHI_CLASS_SCHEMA = { + $schema: 'http://json-schema.org/draft-07/schema#', + $id: '#NHI', + description: 'Non-Human Identity test fixture mirroring data-model NHI.json', + type: 'object', + allOf: [ + { $ref: '#Entity' }, + { + properties: { + _nhiType: { + type: 'string', + enum: [ + 'service_account', + 'credential', + 'secret', + 'oauth_app', + 'bot', + 'certificate', + 'api_key', + 'webhook', + 'ci_cd_identity', + ], + }, + _isAi: { type: 'boolean' }, + _aiConfidence: { + type: 'string', + enum: ['confirmed', 'high', 'medium', 'low'], + }, + _aiPlatform: { type: 'string' }, + _nhiOwner: { type: 'string' }, + _nhiOwnerStatus: { + type: 'string', + enum: ['assigned', 'unassigned', 'orphaned'], + }, + }, + required: [], + }, + ], +}; + +beforeAll(() => { + if (!dataModel.getSchema('NHI')) { + dataModel.IntegrationSchema.addSchema(NHI_CLASS_SCHEMA); + } +}); + const networkSourceData = { id: 'natural-identifier', environment: 'production', @@ -370,3 +422,214 @@ describe('schema validation off', () => { ).not.toThrow(); }); }); + +// AIASM-14: Verify multi-class entity validation for NHI combinations. +// These tests prove the SDK already supports `_class: [, 'NHI']` +// without code changes — the deliverable is a test suite that pins the +// behavior so future refactors don't regress it. +describe('AIASM-14: multi-class NHI combinations', () => { + const NHI_PROPERTIES = [ + '_nhiType', + '_isAi', + '_aiConfidence', + '_aiPlatform', + '_nhiOwner', + '_nhiOwnerStatus', + ]; + + describe('schemaWhitelistedPropertyNames unions properties from each class', () => { + // Entity-base properties present on every J1 class. + const ENTITY_BASE_PROPS = ['id', 'name', 'displayName']; + + test.each([ + ['User', ['username', 'email']], + ['AccessKey', ['fingerprint', 'material', 'usage']], + ['Application', ['COTS', 'SaaS', 'license']], + // Certificate / Secret extend Entity directly without adding properties + // in @jupiterone/data-model 0.62.0 — assert only Entity-base + NHI. + ['Certificate', []], + ['Secret', []], + ])( + '%s + NHI: includes NHI metadata and primary-class properties', + (primaryClass, primarySpecificProps) => { + const props = schemaWhitelistedPropertyNames([primaryClass, 'NHI']); + expect(props).toIncludeAllMembers(NHI_PROPERTIES); + expect(props).toIncludeAllMembers(ENTITY_BASE_PROPS); + if (primarySpecificProps.length) { + expect(props).toIncludeAllMembers(primarySpecificProps); + } + }, + ); + }); + + describe('createIntegrationEntity preserves NHI properties for multi-class entities (BDD 14.5)', () => { + test('User + NHI: keeps _nhiType plus standard User fields (BDD 14.1)', () => { + const entity = createIntegrationEntity({ + entityData: { + assign: { + _class: ['User', 'NHI'], + _type: 'gh_service_user', + _key: 'gh:svc:1', + }, + source: { + id: 'svc-1', + name: 'github-actions-bot', + username: 'gh-actions', + email: 'bot@example.com', + active: true, + _nhiType: 'service_account', + _isAi: false, + _aiConfidence: 'low', + _nhiOwner: 'platform-team', + _nhiOwnerStatus: 'assigned', + }, + }, + }); + + expect(entity._class).toEqual(['User', 'NHI']); + expect(entity).toMatchObject({ + username: 'gh-actions', + email: 'bot@example.com', + _nhiType: 'service_account', + _isAi: false, + _aiConfidence: 'low', + _nhiOwner: 'platform-team', + _nhiOwnerStatus: 'assigned', + }); + }); + + test('AccessKey + NHI: keeps _nhiType=credential plus AccessKey-specific fingerprint (BDD 14.2)', () => { + const entity = createIntegrationEntity({ + entityData: { + assign: { + _class: ['AccessKey', 'NHI'], + _type: 'aws_iam_access_key', + _key: 'aws:ak:1', + }, + source: { + id: 'AKIA-EXAMPLE', + name: 'service-key', + fingerprint: 'sha256:deadbeef', + usage: 'signing', + _nhiType: 'credential', + _isAi: false, + }, + }, + }); + + expect(entity._class).toEqual(['AccessKey', 'NHI']); + expect(entity).toMatchObject({ + fingerprint: 'sha256:deadbeef', + usage: 'signing', + _nhiType: 'credential', + _isAi: false, + }); + }); + + test('Application + NHI: keeps _nhiType=oauth_app and AI metadata', () => { + const entity = createIntegrationEntity({ + entityData: { + assign: { + _class: ['Application', 'NHI'], + _type: 'okta_oauth_app', + _key: 'okta:app:1', + }, + source: { + id: 'okta-app-1', + name: 'Anthropic Claude Bot', + SaaS: true, + license: 'commercial', + _nhiType: 'oauth_app', + _isAi: true, + _aiConfidence: 'confirmed', + _aiPlatform: 'anthropic', + }, + }, + }); + + expect(entity._class).toEqual(['Application', 'NHI']); + expect(entity).toMatchObject({ + SaaS: true, + license: 'commercial', + _nhiType: 'oauth_app', + _isAi: true, + _aiConfidence: 'confirmed', + _aiPlatform: 'anthropic', + }); + }); + + test('Certificate + NHI: keeps _nhiType=certificate alongside Entity-base properties', () => { + const entity = createIntegrationEntity({ + entityData: { + assign: { + _class: ['Certificate', 'NHI'], + _type: 'tls_certificate', + _key: 'cert:1', + }, + source: { + id: 'cert-1', + name: 'service.example.com', + description: 'TLS cert for service', + _nhiType: 'certificate', + }, + }, + }); + + expect(entity._class).toEqual(['Certificate', 'NHI']); + expect(entity).toMatchObject({ + name: 'service.example.com', + description: 'TLS cert for service', + _nhiType: 'certificate', + }); + }); + + test('Secret + NHI: keeps _nhiType=secret alongside Entity-base properties', () => { + const entity = createIntegrationEntity({ + entityData: { + assign: { + _class: ['Secret', 'NHI'], + _type: 'vault_secret', + _key: 'secret:1', + }, + source: { + id: 'secret-1', + name: 'db-password', + description: 'Production database password', + _nhiType: 'secret', + _nhiOwnerStatus: 'orphaned', + }, + }, + }); + + expect(entity._class).toEqual(['Secret', 'NHI']); + expect(entity).toMatchObject({ + name: 'db-password', + description: 'Production database password', + _nhiType: 'secret', + _nhiOwnerStatus: 'orphaned', + }); + }); + + test('omitting all NHI properties is allowed (BDD 14.4: optional)', () => { + const entity = createIntegrationEntity({ + entityData: { + assign: { + _class: ['User', 'NHI'], + _type: 'gh_service_user', + _key: 'gh:svc:2', + }, + source: { + id: 'svc-2', + name: 'no-metadata-yet', + username: 'no-meta', + }, + }, + }); + + expect(entity._class).toEqual(['User', 'NHI']); + for (const prop of NHI_PROPERTIES) { + expect(prop in entity).toBe(false); + } + }); + }); +}); diff --git a/packages/integration-sdk-core/src/data/__tests__/createIntegrationHelpers.test.ts b/packages/integration-sdk-core/src/data/__tests__/createIntegrationHelpers.test.ts index c4e48d4d7..b9b4c6e04 100644 --- a/packages/integration-sdk-core/src/data/__tests__/createIntegrationHelpers.test.ts +++ b/packages/integration-sdk-core/src/data/__tests__/createIntegrationHelpers.test.ts @@ -1,9 +1,52 @@ +import { Type } from '@sinclair/typebox'; import { typeboxClassSchemaMap } from '@jupiterone/data-model'; import { createIntegrationHelpers } from '../createIntegrationHelpers'; import { EntityValidator } from '@jupiterone/integration-sdk-entity-validator'; import { entitySchemas } from '@jupiterone/data-model'; import { SchemaType } from '../..'; +// Inline NHI typebox schema used to verify multi-class+NHI behavior. The +// published @jupiterone/data-model 0.62.0 does not yet include NHI in +// typeboxClassSchemaMap (publishing is AIASM-15). Mirrors the JSON +// equivalent at data-model/.../class_schemas/NHI.json with NHI metadata +// declared as optional. +const NHI_TYPEBOX = Type.Object( + { + _nhiType: Type.Optional( + Type.Union([ + Type.Literal('service_account'), + Type.Literal('credential'), + Type.Literal('secret'), + Type.Literal('oauth_app'), + Type.Literal('bot'), + Type.Literal('certificate'), + Type.Literal('api_key'), + Type.Literal('webhook'), + Type.Literal('ci_cd_identity'), + ]), + ), + _isAi: Type.Optional(Type.Boolean()), + _aiConfidence: Type.Optional( + Type.Union([ + Type.Literal('confirmed'), + Type.Literal('high'), + Type.Literal('medium'), + Type.Literal('low'), + ]), + ), + _aiPlatform: Type.Optional(Type.String()), + _nhiOwner: Type.Optional(Type.String()), + _nhiOwnerStatus: Type.Optional( + Type.Union([ + Type.Literal('assigned'), + Type.Literal('unassigned'), + Type.Literal('orphaned'), + ]), + ), + }, + { $id: '#NHI' }, +); + describe('createIntegrationHelpers', () => { const { createEntityType, @@ -362,4 +405,88 @@ describe('createIntegrationHelpers', () => { expect(errors).toEqual(null); }); + + // AIASM-14: createMultiClassEntityMetadata works with NHI as a secondary + // class. Verifies BDD scenario 14.3 — that the helper accepts an NHI + // schema, produces a multi-class JSON schema, and the resulting + // createEntity callable returns an entity that validates against the + // combined schema. + test('createMultiClassEntityMetadata with NHI: User + NHI multi-class entity validates (BDD 14.3, 14.1)', () => { + const [SVC_USER, createSvcUser] = createMultiClassEntityMetadata({ + resourceName: 'ServiceUser', + _type: 'gh_service_user', + _class: [typeboxClassSchemaMap['User'], NHI_TYPEBOX], + description: 'GitHub service user (User + NHI)', + schema: SchemaType.Object({ + id: SchemaType.String(), + name: SchemaType.String(), + }), + }); + + expect(SVC_USER._class).toEqual(['User', 'NHI']); + expect(SVC_USER._type).toBe('gh_service_user'); + // Schema is a JSON-Schema-shaped object with allOf containing $refs to + // each class plus the literal _class/_type and the user-supplied schema. + expect(SVC_USER.schema).toMatchObject({ + $id: '#gh_service_user', + allOf: expect.arrayContaining([{ $ref: '#User' }, { $ref: '#NHI' }]), + }); + + const entity = createSvcUser({ + id: 'svc-1', + name: 'github-actions-bot', + // Satisfy User-schema required fields. Bots/service-accounts in the + // wild don't always have firstName/lastName — that schema rigidity + // is a data-model concern, not in scope for AIASM-14. + _key: 'gh:svc:account:1', + displayName: 'GitHub Actions', + firstName: 'GitHub', + lastName: 'Actions', + email: ['bot@example.com'], + shortLoginId: ['gh-actions'], + username: ['gh-actions'], + _nhiType: 'service_account', + _isAi: false, + _aiConfidence: 'low', + _nhiOwnerStatus: 'assigned', + }); + expect(entity._class).toEqual(['User', 'NHI']); + expect(entity._type).toBe('gh_service_user'); + + // Validate against the produced schema. The validator needs both class + // schemas registered so the $refs resolve. + const validator = new EntityValidator({ + schemas: [...Object.values(entitySchemas), NHI_TYPEBOX as never], + }); + validator.addSchemas(SVC_USER.schema); + const { errors } = validator.validateEntity(entity); + expect(errors).toEqual(null); + }); + + test('createMultiClassEntityMetadata with NHI: AccessKey + NHI variant (BDD 14.2)', () => { + const [SVC_KEY, createSvcKey] = createMultiClassEntityMetadata({ + resourceName: 'ServiceKey', + _type: 'aws_iam_access_key', + _class: [typeboxClassSchemaMap['AccessKey'], NHI_TYPEBOX], + description: 'AWS IAM access key for an automated workload', + schema: SchemaType.Object({ + id: SchemaType.String(), + name: SchemaType.String(), + }), + }); + + expect(SVC_KEY._class).toEqual(['AccessKey', 'NHI']); + expect(SVC_KEY.schema).toMatchObject({ + allOf: expect.arrayContaining([{ $ref: '#AccessKey' }, { $ref: '#NHI' }]), + }); + + const entity = createSvcKey({ + id: 'AKIA-EXAMPLE', + name: 'service-key', + _key: 'aws:ak:1', + _nhiType: 'credential', + }); + expect(entity._class).toEqual(['AccessKey', 'NHI']); + expect(entity._nhiType).toBe('credential'); + }); }); diff --git a/packages/integration-sdk-testing/src/__tests__/jest.test.ts b/packages/integration-sdk-testing/src/__tests__/jest.test.ts index 176b6528b..89fe48257 100644 --- a/packages/integration-sdk-testing/src/__tests__/jest.test.ts +++ b/packages/integration-sdk-testing/src/__tests__/jest.test.ts @@ -13,7 +13,9 @@ import { createIntegrationHelpers, SchemaType, } from '@jupiterone/integration-sdk-core'; +import * as dataModel from '@jupiterone/data-model'; import { typeboxClassSchemaMap as classSchemaMap } from '@jupiterone/data-model'; +import { getValidatorSync } from '@jupiterone/integration-sdk-entity-validator'; import { getMockIntegrationStep } from '@jupiterone/integration-sdk-private-test-utils'; import { randomUUID as uuid } from 'crypto'; import { toMatchStepMetadata } from '..'; @@ -652,6 +654,115 @@ describe('#toMatchDataModelSchema', () => { expect(result.pass).toBe(false); }); + + // AIASM-14 / BDD 14.4: toMatchDataModelSchema validates a User+NHI + // multi-class entity. Both class schemas must be exercised, and the + // entity must pass. + describe('AIASM-14: multi-class NHI', () => { + const NHI_JSON_SCHEMA = { + $schema: 'http://json-schema.org/draft-07/schema#', + $id: '#NHI', + type: 'object', + allOf: [ + { $ref: '#Entity' }, + { + properties: { + _nhiType: { + type: 'string', + enum: [ + 'service_account', + 'credential', + 'secret', + 'oauth_app', + 'bot', + 'certificate', + 'api_key', + 'webhook', + 'ci_cd_identity', + ], + }, + _isAi: { type: 'boolean' }, + _aiConfidence: { + type: 'string', + enum: ['confirmed', 'high', 'medium', 'low'], + }, + _aiPlatform: { type: 'string' }, + _nhiOwner: { type: 'string' }, + _nhiOwnerStatus: { + type: 'string', + enum: ['assigned', 'unassigned', 'orphaned'], + }, + }, + required: [], + }, + ], + }; + + beforeAll(() => { + // Register the inline NHI schema with both the data-model singleton and + // the validator singleton used by toMatchDataModelSchema. The published + // @jupiterone/data-model 0.62.0 does not yet ship NHI (AIASM-15). + if (!dataModel.getSchema('NHI')) { + dataModel.IntegrationSchema.addSchema(NHI_JSON_SCHEMA); + } + getValidatorSync().addSchemas(NHI_JSON_SCHEMA); + }); + + test('passes for User + NHI multi-class entity (BDD 14.4)', () => { + const SVC_USER_METADATA = { + resourceName: 'ServiceUser', + _type: 'gh_service_user_match', + _class: ['User', 'NHI'], + schema: { + $id: '#gh_service_user_match', + description: 'GitHub service user — multi-class User + NHI', + allOf: [ + { $ref: '#User' }, + { $ref: '#NHI' }, + { + properties: { + _class: { + type: 'array', + items: [ + { const: 'User', type: 'string' }, + { const: 'NHI', type: 'string' }, + ], + additionalItems: false, + maxItems: 2, + minItems: 2, + }, + _type: { const: 'gh_service_user_match', type: 'string' }, + }, + type: 'object', + required: ['_class', '_type'], + }, + ], + }, + }; + + const entity: Entity = { + _key: 'gh:svc:account:match:1', + _type: 'gh_service_user_match', + _class: ['User', 'NHI'], + name: 'github-actions-bot', + // User-class required fields + displayName: 'GitHub Actions', + firstName: 'GitHub', + lastName: 'Actions', + email: ['bot@example.com'], + shortLoginId: ['gh-actions'], + username: ['gh-actions'], + // NHI-class metadata + _nhiType: 'service_account', + _isAi: false, + _aiConfidence: 'low', + _nhiOwnerStatus: 'assigned', + }; + + const result = toMatchDataModelSchema(entity, SVC_USER_METADATA); + expect(result.pass).toBe(true); + }); + }); }); describe('#toMatchDirectRelationshipSchema', () => { From 4bd5dcd12168efbbb1ed0ec5088a202dce396d40 Mon Sep 17 00:00:00 2001 From: James Mountifield Date: Wed, 29 Apr 2026 17:32:16 +0100 Subject: [PATCH 2/2] refactor(test): drop underscore prefix from inline NHI fixtures, use Entity owner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tracks data-model PR #104 review: - Inline NHI schemas in all three test files now use bare property names (nhiType, isAi, aiConfidence, aiPlatform, nhiOwnerStatus) matching the data-model NHI.json convention. Underscore prefix is reserved for system-managed fields (_class, _type, _key). - Drop the _nhiOwner property entirely. The data-model schema redeclares Entity's existing `owner` field with an NHI-specific description rather than introducing a separate property — the inline test fixtures now match that shape. - Update test data and assertions accordingly. NHI_PROPERTIES whitelist array drops _nhiOwner. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../__tests__/createIntegrationEntity.test.ts | 86 +++++++++---------- .../createIntegrationHelpers.test.ts | 23 +++-- .../src/__tests__/jest.test.ts | 19 ++-- 3 files changed, 61 insertions(+), 67 deletions(-) diff --git a/packages/integration-sdk-core/src/data/__tests__/createIntegrationEntity.test.ts b/packages/integration-sdk-core/src/data/__tests__/createIntegrationEntity.test.ts index f91e0b36f..426ea12df 100644 --- a/packages/integration-sdk-core/src/data/__tests__/createIntegrationEntity.test.ts +++ b/packages/integration-sdk-core/src/data/__tests__/createIntegrationEntity.test.ts @@ -22,7 +22,7 @@ const NHI_CLASS_SCHEMA = { { $ref: '#Entity' }, { properties: { - _nhiType: { + nhiType: { type: 'string', enum: [ 'service_account', @@ -36,14 +36,13 @@ const NHI_CLASS_SCHEMA = { 'ci_cd_identity', ], }, - _isAi: { type: 'boolean' }, - _aiConfidence: { + isAi: { type: 'boolean' }, + aiConfidence: { type: 'string', enum: ['confirmed', 'high', 'medium', 'low'], }, - _aiPlatform: { type: 'string' }, - _nhiOwner: { type: 'string' }, - _nhiOwnerStatus: { + aiPlatform: { type: 'string' }, + nhiOwnerStatus: { type: 'string', enum: ['assigned', 'unassigned', 'orphaned'], }, @@ -429,12 +428,11 @@ describe('schema validation off', () => { // behavior so future refactors don't regress it. describe('AIASM-14: multi-class NHI combinations', () => { const NHI_PROPERTIES = [ - '_nhiType', - '_isAi', - '_aiConfidence', - '_aiPlatform', - '_nhiOwner', - '_nhiOwnerStatus', + 'nhiType', + 'isAi', + 'aiConfidence', + 'aiPlatform', + 'nhiOwnerStatus', ]; describe('schemaWhitelistedPropertyNames unions properties from each class', () => { @@ -463,7 +461,7 @@ describe('AIASM-14: multi-class NHI combinations', () => { }); describe('createIntegrationEntity preserves NHI properties for multi-class entities (BDD 14.5)', () => { - test('User + NHI: keeps _nhiType plus standard User fields (BDD 14.1)', () => { + test('User + NHI: keeps nhiType plus standard User fields (BDD 14.1)', () => { const entity = createIntegrationEntity({ entityData: { assign: { @@ -477,11 +475,10 @@ describe('AIASM-14: multi-class NHI combinations', () => { username: 'gh-actions', email: 'bot@example.com', active: true, - _nhiType: 'service_account', - _isAi: false, - _aiConfidence: 'low', - _nhiOwner: 'platform-team', - _nhiOwnerStatus: 'assigned', + nhiType: 'service_account', + isAi: false, + aiConfidence: 'low', + nhiOwnerStatus: 'assigned', }, }, }); @@ -490,15 +487,14 @@ describe('AIASM-14: multi-class NHI combinations', () => { expect(entity).toMatchObject({ username: 'gh-actions', email: 'bot@example.com', - _nhiType: 'service_account', - _isAi: false, - _aiConfidence: 'low', - _nhiOwner: 'platform-team', - _nhiOwnerStatus: 'assigned', + nhiType: 'service_account', + isAi: false, + aiConfidence: 'low', + nhiOwnerStatus: 'assigned', }); }); - test('AccessKey + NHI: keeps _nhiType=credential plus AccessKey-specific fingerprint (BDD 14.2)', () => { + test('AccessKey + NHI: keeps nhiType=credential plus AccessKey-specific fingerprint (BDD 14.2)', () => { const entity = createIntegrationEntity({ entityData: { assign: { @@ -511,8 +507,8 @@ describe('AIASM-14: multi-class NHI combinations', () => { name: 'service-key', fingerprint: 'sha256:deadbeef', usage: 'signing', - _nhiType: 'credential', - _isAi: false, + nhiType: 'credential', + isAi: false, }, }, }); @@ -521,12 +517,12 @@ describe('AIASM-14: multi-class NHI combinations', () => { expect(entity).toMatchObject({ fingerprint: 'sha256:deadbeef', usage: 'signing', - _nhiType: 'credential', - _isAi: false, + nhiType: 'credential', + isAi: false, }); }); - test('Application + NHI: keeps _nhiType=oauth_app and AI metadata', () => { + test('Application + NHI: keeps nhiType=oauth_app and AI metadata', () => { const entity = createIntegrationEntity({ entityData: { assign: { @@ -539,10 +535,10 @@ describe('AIASM-14: multi-class NHI combinations', () => { name: 'Anthropic Claude Bot', SaaS: true, license: 'commercial', - _nhiType: 'oauth_app', - _isAi: true, - _aiConfidence: 'confirmed', - _aiPlatform: 'anthropic', + nhiType: 'oauth_app', + isAi: true, + aiConfidence: 'confirmed', + aiPlatform: 'anthropic', }, }, }); @@ -551,14 +547,14 @@ describe('AIASM-14: multi-class NHI combinations', () => { expect(entity).toMatchObject({ SaaS: true, license: 'commercial', - _nhiType: 'oauth_app', - _isAi: true, - _aiConfidence: 'confirmed', - _aiPlatform: 'anthropic', + nhiType: 'oauth_app', + isAi: true, + aiConfidence: 'confirmed', + aiPlatform: 'anthropic', }); }); - test('Certificate + NHI: keeps _nhiType=certificate alongside Entity-base properties', () => { + test('Certificate + NHI: keeps nhiType=certificate alongside Entity-base properties', () => { const entity = createIntegrationEntity({ entityData: { assign: { @@ -570,7 +566,7 @@ describe('AIASM-14: multi-class NHI combinations', () => { id: 'cert-1', name: 'service.example.com', description: 'TLS cert for service', - _nhiType: 'certificate', + nhiType: 'certificate', }, }, }); @@ -579,11 +575,11 @@ describe('AIASM-14: multi-class NHI combinations', () => { expect(entity).toMatchObject({ name: 'service.example.com', description: 'TLS cert for service', - _nhiType: 'certificate', + nhiType: 'certificate', }); }); - test('Secret + NHI: keeps _nhiType=secret alongside Entity-base properties', () => { + test('Secret + NHI: keeps nhiType=secret alongside Entity-base properties', () => { const entity = createIntegrationEntity({ entityData: { assign: { @@ -595,8 +591,8 @@ describe('AIASM-14: multi-class NHI combinations', () => { id: 'secret-1', name: 'db-password', description: 'Production database password', - _nhiType: 'secret', - _nhiOwnerStatus: 'orphaned', + nhiType: 'secret', + nhiOwnerStatus: 'orphaned', }, }, }); @@ -605,8 +601,8 @@ describe('AIASM-14: multi-class NHI combinations', () => { expect(entity).toMatchObject({ name: 'db-password', description: 'Production database password', - _nhiType: 'secret', - _nhiOwnerStatus: 'orphaned', + nhiType: 'secret', + nhiOwnerStatus: 'orphaned', }); }); diff --git a/packages/integration-sdk-core/src/data/__tests__/createIntegrationHelpers.test.ts b/packages/integration-sdk-core/src/data/__tests__/createIntegrationHelpers.test.ts index b9b4c6e04..d0bc417fa 100644 --- a/packages/integration-sdk-core/src/data/__tests__/createIntegrationHelpers.test.ts +++ b/packages/integration-sdk-core/src/data/__tests__/createIntegrationHelpers.test.ts @@ -12,7 +12,7 @@ import { SchemaType } from '../..'; // declared as optional. const NHI_TYPEBOX = Type.Object( { - _nhiType: Type.Optional( + nhiType: Type.Optional( Type.Union([ Type.Literal('service_account'), Type.Literal('credential'), @@ -25,8 +25,8 @@ const NHI_TYPEBOX = Type.Object( Type.Literal('ci_cd_identity'), ]), ), - _isAi: Type.Optional(Type.Boolean()), - _aiConfidence: Type.Optional( + isAi: Type.Optional(Type.Boolean()), + aiConfidence: Type.Optional( Type.Union([ Type.Literal('confirmed'), Type.Literal('high'), @@ -34,9 +34,8 @@ const NHI_TYPEBOX = Type.Object( Type.Literal('low'), ]), ), - _aiPlatform: Type.Optional(Type.String()), - _nhiOwner: Type.Optional(Type.String()), - _nhiOwnerStatus: Type.Optional( + aiPlatform: Type.Optional(Type.String()), + nhiOwnerStatus: Type.Optional( Type.Union([ Type.Literal('assigned'), Type.Literal('unassigned'), @@ -445,10 +444,10 @@ describe('createIntegrationHelpers', () => { email: ['bot@example.com'], shortLoginId: ['gh-actions'], username: ['gh-actions'], - _nhiType: 'service_account', - _isAi: false, - _aiConfidence: 'low', - _nhiOwnerStatus: 'assigned', + nhiType: 'service_account', + isAi: false, + aiConfidence: 'low', + nhiOwnerStatus: 'assigned', }); expect(entity._class).toEqual(['User', 'NHI']); expect(entity._type).toBe('gh_service_user'); @@ -484,9 +483,9 @@ describe('createIntegrationHelpers', () => { id: 'AKIA-EXAMPLE', name: 'service-key', _key: 'aws:ak:1', - _nhiType: 'credential', + nhiType: 'credential', }); expect(entity._class).toEqual(['AccessKey', 'NHI']); - expect(entity._nhiType).toBe('credential'); + expect(entity.nhiType).toBe('credential'); }); }); diff --git a/packages/integration-sdk-testing/src/__tests__/jest.test.ts b/packages/integration-sdk-testing/src/__tests__/jest.test.ts index 89fe48257..44ff3bc79 100644 --- a/packages/integration-sdk-testing/src/__tests__/jest.test.ts +++ b/packages/integration-sdk-testing/src/__tests__/jest.test.ts @@ -667,7 +667,7 @@ describe('#toMatchDataModelSchema', () => { { $ref: '#Entity' }, { properties: { - _nhiType: { + nhiType: { type: 'string', enum: [ 'service_account', @@ -681,14 +681,13 @@ describe('#toMatchDataModelSchema', () => { 'ci_cd_identity', ], }, - _isAi: { type: 'boolean' }, - _aiConfidence: { + isAi: { type: 'boolean' }, + aiConfidence: { type: 'string', enum: ['confirmed', 'high', 'medium', 'low'], }, - _aiPlatform: { type: 'string' }, - _nhiOwner: { type: 'string' }, - _nhiOwnerStatus: { + aiPlatform: { type: 'string' }, + nhiOwnerStatus: { type: 'string', enum: ['assigned', 'unassigned', 'orphaned'], }, @@ -753,10 +752,10 @@ describe('#toMatchDataModelSchema', () => { shortLoginId: ['gh-actions'], username: ['gh-actions'], // NHI-class metadata - _nhiType: 'service_account', - _isAi: false, - _aiConfidence: 'low', - _nhiOwnerStatus: 'assigned', + nhiType: 'service_account', + isAi: false, + aiConfidence: 'low', + nhiOwnerStatus: 'assigned', }; const result = toMatchDataModelSchema(entity, SVC_USER_METADATA);