godcrm/backend/__tests__/connectors/connectors.integration.test.js
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

143 lines
4.7 KiB
JavaScript

// @vitest-environment node
/**
* ADR-0028 Phase 1 — Space Connectors integration tests.
*
* Boot guard MUST run before any DB connection (ADR-0009). vitest.config
* already lists backend/test/setup.js first; we re-import here so a direct
* `vitest run <this-file>` still aborts on PROD DB.
*/
import './../../test/setup.js';
import crypto from 'crypto';
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
// Provide a vault key for the test run. Real env (.env) takes precedence if set.
if (!process.env.CRM_CREDENTIAL_KEY) {
process.env.CRM_CREDENTIAL_KEY = crypto.randomBytes(32).toString('hex');
}
if (!process.env.JWT_SECRET) {
process.env.JWT_SECRET = 'test-secret-' + crypto.randomBytes(8).toString('hex');
}
// Import after env is set up.
const { default: vault, getSpaceConnector } = await import(
'../../services/connectors/CredentialVault.js'
);
const {
getConnectorType,
listConnectorTypes,
validateConnectorTypeBody,
} = await import('../../services/connectors/catalogue/index.js');
describe('CredentialVault', () => {
beforeAll(async () => {
await vault.init();
});
afterAll(async () => {
await vault.shutdown();
});
it('encrypt/decrypt round-trip preserves payload', () => {
const plain = {
access_token: 'tok-xyz',
refresh_token: 'rfr-abc',
raw: { account: 'me@example.com', expires_in: 3600 },
};
const enc = vault.encrypt(plain);
expect(enc.v).toBe(1);
expect(typeof enc.iv).toBe('string');
expect(typeof enc.tag).toBe('string');
expect(typeof enc.ct).toBe('string');
const dec = vault.decrypt(enc);
expect(dec).toEqual(plain);
});
it('decrypt rejects tampered ciphertext', () => {
const enc = vault.encrypt({ x: 1 });
enc.ct = Buffer.from('tampered').toString('base64');
expect(() => vault.decrypt(enc)).toThrow();
});
it('decrypt rejects unknown version', () => {
const enc = vault.encrypt({ x: 1 });
enc.v = 99;
expect(() => vault.decrypt(enc)).toThrow(/version/);
});
it('health reports configured key', () => {
const h = vault.health();
expect(h.ok).toBe(true);
expect(h.hasKey).toBe(true);
expect(h.keyVersion).toBe(1);
});
});
describe('Connector catalogue', () => {
it('lists 4 types in Phase 1', () => {
const slugs = listConnectorTypes().map((t) => t.slug).sort();
expect(slugs).toEqual(['custom_api_key', 'custom_oauth2', 'figma', 'notion']);
});
it('figma is oauth2 with correct URLs', () => {
const f = getConnectorType('figma');
expect(f.auth_kind).toBe('oauth2');
expect(f.authorize_url).toBe('https://www.figma.com/oauth');
expect(f.token_url).toBe('https://api.figma.com/v1/oauth/token');
expect(f.refresh_supported).toBe(true);
});
it('notion exposes buildAuthorizeUrl override with owner=user', () => {
const n = getConnectorType('notion');
expect(typeof n.buildAuthorizeUrl).toBe('function');
const url = n.buildAuthorizeUrl({
state: 'state123',
redirect_uri: 'https://devcrm.hltrn.cc/api/v3/connectors/oauth/callback',
client_id: 'cid',
});
const u = new URL(url);
expect(u.searchParams.get('owner')).toBe('user');
expect(u.searchParams.get('client_id')).toBe('cid');
expect(u.searchParams.get('state')).toBe('state123');
});
it('custom_oauth2 validates required custom_definition', () => {
expect(validateConnectorTypeBody('custom_oauth2', {}).ok).toBe(false);
expect(
validateConnectorTypeBody('custom_oauth2', {
custom_definition: { client_id: 'x', authorize_url: 'https://a.com', token_url: 'https://b.com' },
fields: { client_secret: 's' },
}).ok
).toBe(true);
});
it('custom_api_key requires fields.api_key', () => {
expect(validateConnectorTypeBody('custom_api_key', {}).ok).toBe(false);
expect(
validateConnectorTypeBody('custom_api_key', { fields: { api_key: 'k1' } }).ok
).toBe(true);
});
it('rejects unknown type', () => {
expect(validateConnectorTypeBody('whatever', {}).ok).toBe(false);
});
});
describe('getSpaceConnector helper', () => {
it('returns null when no row exists for (space, type)', async () => {
// Use a deliberately non-existent space. Skips if migration 054 hasn't run
// against the local test DB yet (`make sync-db && npx knex migrate:latest`).
try {
const result = await getSpaceConnector(-9999, 'figma');
expect(result).toBeNull();
} catch (err) {
if (/relation "space_connectors" does not exist/i.test(err?.message || '')) {
// eslint-disable-next-line no-console
console.warn(
'[connectors test] Skipping getSpaceConnector check — migration 054 not applied to test DB yet.'
);
return;
}
throw err;
}
});
});