Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

173 lines
6.1 KiB
TypeScript

import { existsSync, readFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';
const testDir = path.dirname(fileURLToPath(import.meta.url));
const packageRoot = path.resolve(testDir, '..');
function read(filePath: string): string {
return readFileSync(path.join(packageRoot, filePath), 'utf8');
}
describe('Documentation coverage', () => {
it('T043: README includes credential setup, operation matrix, and ticket field requirements', () => {
const readme = read('README.md');
expect(readme).toContain('## Credential Setup');
expect(readme).toContain('## Operation Matrix');
expect(readme).toContain('## Ticket Field Requirements');
expect(readme).toContain('## Contact Field Requirements');
expect(readme).toContain('baseUrl');
expect(readme).toContain('apiKey');
});
it('T044: README installation covers self-hosted npm/manual paths and cloud limitation', () => {
const readme = read('README.md');
expect(readme).toContain('Self-hosted n8n');
expect(readme).toContain('npm install n8n-nodes-alga-psa');
expect(readme).toContain('unverified');
expect(readme).toContain('n8n Cloud');
});
it('T045: example workflows include create->update-assignment and search->update-status', () => {
const createPath = path.join(packageRoot, 'examples/create-update-assignment.workflow.json');
const searchPath = path.join(packageRoot, 'examples/search-update-status.workflow.json');
expect(existsSync(createPath)).toBe(true);
expect(existsSync(searchPath)).toBe(true);
const createWorkflow = JSON.parse(readFileSync(createPath, 'utf8')) as {
nodes: Array<{ parameters?: Record<string, unknown> }>;
};
const searchWorkflow = JSON.parse(readFileSync(searchPath, 'utf8')) as {
nodes: Array<{ parameters?: Record<string, unknown> }>;
};
expect(
createWorkflow.nodes.some(
(node) => node.parameters?.ticketOperation === 'create',
),
).toBe(true);
expect(
createWorkflow.nodes.some(
(node) => node.parameters?.ticketOperation === 'updateAssignment',
),
).toBe(true);
expect(
searchWorkflow.nodes.some(
(node) => node.parameters?.ticketOperation === 'search',
),
).toBe(true);
expect(
searchWorkflow.nodes.some(
(node) => node.parameters?.ticketOperation === 'updateStatus',
),
).toBe(true);
});
it('T053: README documents ticket comment operations without unsupported time_spent input', () => {
const readme = read('README.md');
const examplePath = path.join(packageRoot, 'examples/add-comment-then-list-comments.workflow.json');
expect(readme).toContain('## Ticket Comment Operations');
expect(readme).toContain('List Comments');
expect(readme).toContain('Add Comment');
expect(readme).toContain('time_spent');
expect(readme).toContain('not exposed');
expect(existsSync(examplePath)).toBe(true);
const commentWorkflow = JSON.parse(readFileSync(examplePath, 'utf8')) as {
nodes: Array<{ parameters?: Record<string, unknown> }>;
};
expect(
commentWorkflow.nodes.some(
(node) => node.parameters?.ticketOperation === 'addComment',
),
).toBe(true);
expect(
commentWorkflow.nodes.some(
(node) => node.parameters?.ticketOperation === 'listComments',
),
).toBe(true);
});
it('T034: README operation matrix includes contact CRUD operations', () => {
const readme = read('README.md');
expect(readme).toContain('| Contact | Create, Get, List, Update, Delete |');
});
it('T035: README describes contact field scope, lookup behavior, and list/delete output expectations', () => {
const readme = read('README.md');
expect(readme).toContain('full_name');
expect(readme).toContain('phone_numbers');
expect(readme).toContain('From List');
expect(readme).toContain('By ID');
expect(readme).toContain('Contact -> List');
expect(readme).toContain('Contact -> Delete');
});
it('T036: contact example workflow is present and referenced by the README', () => {
const readme = read('README.md');
const examplePath = path.join(packageRoot, 'examples/create-update-contact.workflow.json');
expect(readme).toContain('examples/create-update-contact.workflow.json');
expect(existsSync(examplePath)).toBe(true);
const workflow = JSON.parse(readFileSync(examplePath, 'utf8')) as {
nodes: Array<{ parameters?: Record<string, unknown> }>;
};
expect(
workflow.nodes.some(
(node) => node.parameters?.contactOperation === 'create',
),
).toBe(true);
expect(
workflow.nodes.some(
(node) => node.parameters?.contactOperation === 'update',
),
).toBe(true);
});
it('T037: release notes mention the contact CRUD expansion and its first-pass scope', () => {
const releaseNotes = read('RELEASE_NOTES.md');
expect(releaseNotes).toContain('Contact');
expect(releaseNotes).toContain('Create');
expect(releaseNotes).toContain('Get');
expect(releaseNotes).toContain('List');
expect(releaseNotes).toContain('Update');
expect(releaseNotes).toContain('Delete');
expect(releaseNotes).toContain('phone_numbers');
});
it('T042: contact docs and example workflow show the hybrid email fields', () => {
const readme = read('README.md');
const example = JSON.parse(read('examples/create-update-contact.workflow.json')) as {
nodes: Array<{ parameters?: Record<string, any> }>;
};
expect(readme).toContain('primary_email_canonical_type');
expect(readme).toContain('primary_email_custom_type');
expect(readme).toContain('additional_email_addresses');
const createNode = example.nodes.find(
(node) => node.parameters?.contactOperation === 'create',
);
expect(createNode?.parameters?.contactCreateAdditionalFields).toMatchObject({
primary_email_canonical_type: 'billing',
});
expect(
String(
createNode?.parameters?.contactCreateAdditionalFields?.additional_email_addresses ?? '',
),
).toContain('ada.personal@example.com');
});
});