mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-07 02:58:02 +00:00
test(cobol): add 26 integration tests with exact assertions + fix CICS resolution bug
Integration tests (test/integration/resolvers/cobol.test.ts): - 26 tests covering full COBOL system extraction - ALL assertions use exact toBe(N) — zero fuzzy assertions - Fixtures: CUSTUPDT.cbl, AUDITLOG.cbl, CUSTDAT.cpy, RPTGEN.cbl, RUNJOBS.jcl Bug fix (cobol-processor.ts): - CICS LINK/XCTL cross-program resolution was broken — edges were created with "resolved" reason but pointing to <unresolved> targets - Fix: use cics-link-unresolved / cics-xctl-unresolved suffix pattern matching the existing cobol-call-unresolved pattern - Second-pass resolver now patches both CALL and CICS unresolved edges All 3915 tests pass, 0 failures.
This commit is contained in:
parent
9760f966cb
commit
41b0d8dfad
7 changed files with 441 additions and 19 deletions
|
|
@ -183,23 +183,35 @@ export const processCobol = (
|
|||
// (target = <unresolved>:PROGNAME) because later programs haven't
|
||||
// been registered in moduleNodeIds yet. Now that ALL programs are
|
||||
// processed, re-scan unresolved CALLS edges and patch them.
|
||||
// This covers both `cobol-call-unresolved` and CICS LINK/XCTL edges
|
||||
// whose targets contain `<unresolved>:`.
|
||||
graph.forEachRelationship(rel => {
|
||||
if (rel.type === 'CALLS' && rel.reason?.startsWith('cobol-call-unresolved')) {
|
||||
// Extract the program name from the synthetic target ID
|
||||
const match = rel.targetId.match(/<unresolved>:(.+)/);
|
||||
if (!match) return;
|
||||
const resolvedId = moduleNodeIds.get(match[1]);
|
||||
if (resolvedId) {
|
||||
// Replace with resolved edge (can't mutate, so add new + mark old)
|
||||
graph.addRelationship({
|
||||
id: rel.id + ':resolved',
|
||||
type: 'CALLS',
|
||||
sourceId: rel.sourceId,
|
||||
targetId: resolvedId,
|
||||
confidence: 0.95,
|
||||
reason: 'cobol-call',
|
||||
});
|
||||
}
|
||||
if (rel.type !== 'CALLS') return;
|
||||
const match = rel.targetId.match(/<unresolved>:(.+)/);
|
||||
if (!match) return;
|
||||
const resolvedId = moduleNodeIds.get(match[1]);
|
||||
if (!resolvedId) return;
|
||||
|
||||
if (rel.reason?.startsWith('cobol-call-unresolved')) {
|
||||
// Replace unresolved CALL with resolved edge
|
||||
graph.addRelationship({
|
||||
id: rel.id + ':resolved',
|
||||
type: 'CALLS',
|
||||
sourceId: rel.sourceId,
|
||||
targetId: resolvedId,
|
||||
confidence: 0.95,
|
||||
reason: 'cobol-call',
|
||||
});
|
||||
} else if (rel.reason === 'cics-link-unresolved' || rel.reason === 'cics-xctl-unresolved') {
|
||||
// Replace unresolved CICS LINK/XCTL with resolved edge
|
||||
graph.addRelationship({
|
||||
id: rel.id + ':resolved',
|
||||
type: 'CALLS',
|
||||
sourceId: rel.sourceId,
|
||||
targetId: resolvedId,
|
||||
confidence: 0.95,
|
||||
reason: rel.reason.replace('-unresolved', ''),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -493,15 +505,17 @@ function mapToGraph(
|
|||
});
|
||||
// LINK/XCTL -> cross-program CALLS
|
||||
if (cics.programName && (cics.command === 'LINK' || cics.command === 'XCTL')) {
|
||||
const targetId = moduleNodeIds.get(cics.programName.toUpperCase())
|
||||
const cicsTargetModuleId = moduleNodeIds.get(cics.programName.toUpperCase());
|
||||
const targetId = cicsTargetModuleId
|
||||
?? generateId('Module', `<unresolved>:${cics.programName.toUpperCase()}`);
|
||||
const cicsReason = `cics-${cics.command.toLowerCase()}`;
|
||||
graph.addRelationship({
|
||||
id: generateId('CALLS', `${parentId}->cics-${cics.command.toLowerCase()}->${cics.programName}:L${cics.line}`),
|
||||
type: 'CALLS',
|
||||
sourceId: parentId,
|
||||
targetId,
|
||||
confidence: 0.95,
|
||||
reason: `cics-${cics.command.toLowerCase()}`,
|
||||
confidence: cicsTargetModuleId ? 0.95 : 0.5,
|
||||
reason: cicsTargetModuleId ? cicsReason : `${cicsReason}-unresolved`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
21
gitnexus/test/fixtures/lang-resolution/cobol-app/AUDITLOG.cbl
vendored
Normal file
21
gitnexus/test/fixtures/lang-resolution/cobol-app/AUDITLOG.cbl
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
IDENTIFICATION DIVISION.
|
||||
PROGRAM-ID. AUDITLOG.
|
||||
|
||||
DATA DIVISION.
|
||||
WORKING-STORAGE SECTION.
|
||||
01 WS-LOG-MESSAGE PIC X(80).
|
||||
01 WS-TIMESTAMP PIC X(26).
|
||||
|
||||
LINKAGE SECTION.
|
||||
01 LS-CUST-ID PIC 9(8).
|
||||
01 LS-AMOUNT PIC 9(7)V99.
|
||||
|
||||
PROCEDURE DIVISION USING LS-CUST-ID LS-AMOUNT.
|
||||
MAIN-PARAGRAPH.
|
||||
PERFORM WRITE-LOG
|
||||
GOBACK.
|
||||
|
||||
WRITE-LOG.
|
||||
STRING 'Customer ' LS-CUST-ID ' amount ' LS-AMOUNT
|
||||
DELIMITED BY SIZE INTO WS-LOG-MESSAGE
|
||||
DISPLAY WS-LOG-MESSAGE.
|
||||
6
gitnexus/test/fixtures/lang-resolution/cobol-app/CUSTDAT.cpy
vendored
Normal file
6
gitnexus/test/fixtures/lang-resolution/cobol-app/CUSTDAT.cpy
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
01 WS-CUSTOMER-DATA.
|
||||
05 WS-CUST-CODE PIC X(10).
|
||||
05 WS-CUST-TYPE PIC X(3).
|
||||
88 PREMIUM-CUSTOMER VALUE 'PRM'.
|
||||
88 REGULAR-CUSTOMER VALUE 'REG'.
|
||||
05 WS-CUST-ADDR PIC X(50).
|
||||
60
gitnexus/test/fixtures/lang-resolution/cobol-app/CUSTUPDT.cbl
vendored
Normal file
60
gitnexus/test/fixtures/lang-resolution/cobol-app/CUSTUPDT.cbl
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
IDENTIFICATION DIVISION.
|
||||
PROGRAM-ID. CUSTUPDT.
|
||||
AUTHOR. TEST.
|
||||
|
||||
ENVIRONMENT DIVISION.
|
||||
INPUT-OUTPUT SECTION.
|
||||
FILE-CONTROL.
|
||||
SELECT CUSTOMER-FILE ASSIGN TO 'CUSTFILE'
|
||||
ORGANIZATION IS INDEXED
|
||||
ACCESS IS DYNAMIC
|
||||
RECORD KEY IS CUST-ID
|
||||
FILE STATUS IS WS-FILE-STATUS.
|
||||
|
||||
DATA DIVISION.
|
||||
FILE SECTION.
|
||||
FD CUSTOMER-FILE.
|
||||
01 CUSTOMER-RECORD.
|
||||
05 CUST-ID PIC 9(8).
|
||||
05 CUST-NAME PIC X(30).
|
||||
05 CUST-BALANCE PIC 9(7)V99.
|
||||
|
||||
WORKING-STORAGE SECTION.
|
||||
01 WS-FILE-STATUS PIC XX.
|
||||
01 WS-CUSTOMER-NAME PIC X(30).
|
||||
01 WS-AMOUNT PIC 9(7)V99.
|
||||
01 WS-EOF PIC 9 VALUE 0.
|
||||
88 END-OF-FILE VALUE 1.
|
||||
|
||||
PROCEDURE DIVISION.
|
||||
MAIN-PARAGRAPH.
|
||||
PERFORM INIT-PARAGRAPH
|
||||
PERFORM PROCESS-PARAGRAPH
|
||||
PERFORM CLEANUP-PARAGRAPH
|
||||
STOP RUN.
|
||||
|
||||
INIT-PARAGRAPH.
|
||||
OPEN I-O CUSTOMER-FILE
|
||||
MOVE SPACES TO WS-CUSTOMER-NAME.
|
||||
|
||||
PROCESS-PARAGRAPH.
|
||||
PERFORM READ-CUSTOMER
|
||||
PERFORM UPDATE-BALANCE
|
||||
CALL "AUDITLOG" USING CUST-ID WS-AMOUNT
|
||||
PERFORM WRITE-CUSTOMER.
|
||||
|
||||
READ-CUSTOMER.
|
||||
READ CUSTOMER-FILE
|
||||
NOT AT END
|
||||
MOVE CUST-NAME TO WS-CUSTOMER-NAME
|
||||
END-READ.
|
||||
|
||||
UPDATE-BALANCE.
|
||||
ADD WS-AMOUNT TO CUST-BALANCE
|
||||
MOVE WS-AMOUNT TO CUST-BALANCE.
|
||||
|
||||
WRITE-CUSTOMER.
|
||||
REWRITE CUSTOMER-RECORD.
|
||||
|
||||
CLEANUP-PARAGRAPH.
|
||||
CLOSE CUSTOMER-FILE.
|
||||
36
gitnexus/test/fixtures/lang-resolution/cobol-app/RPTGEN.cbl
vendored
Normal file
36
gitnexus/test/fixtures/lang-resolution/cobol-app/RPTGEN.cbl
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
IDENTIFICATION DIVISION.
|
||||
PROGRAM-ID. RPTGEN.
|
||||
|
||||
DATA DIVISION.
|
||||
WORKING-STORAGE SECTION.
|
||||
COPY CUSTDAT.
|
||||
01 WS-REPORT-LINE PIC X(132).
|
||||
01 WS-SQL-CODE PIC S9(9) COMP.
|
||||
|
||||
PROCEDURE DIVISION.
|
||||
MAIN-PARAGRAPH.
|
||||
PERFORM FETCH-DATA
|
||||
PERFORM FORMAT-REPORT
|
||||
PERFORM SEND-SCREEN
|
||||
CALL "CUSTUPDT"
|
||||
STOP RUN.
|
||||
|
||||
FETCH-DATA.
|
||||
EXEC SQL
|
||||
SELECT CUST_NAME, CUST_BALANCE
|
||||
FROM CUSTOMER
|
||||
WHERE CUST_ID = :WS-CUST-CODE
|
||||
END-EXEC.
|
||||
|
||||
FORMAT-REPORT.
|
||||
MOVE WS-CUST-CODE TO WS-REPORT-LINE
|
||||
PERFORM MAIN-PARAGRAPH THRU FORMAT-REPORT.
|
||||
|
||||
SEND-SCREEN.
|
||||
EXEC CICS
|
||||
SEND MAP('CUSTRPT') MAPSET('CUSTSET')
|
||||
END-EXEC.
|
||||
|
||||
EXEC CICS
|
||||
LINK PROGRAM('AUDITLOG')
|
||||
END-EXEC.
|
||||
5
gitnexus/test/fixtures/lang-resolution/cobol-app/RUNJOBS.jcl
vendored
Normal file
5
gitnexus/test/fixtures/lang-resolution/cobol-app/RUNJOBS.jcl
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
//CUSTJOB JOB (ACCT),'CUSTOMER UPDATE',CLASS=A,MSGCLASS=X
|
||||
//STEP1 EXEC PGM=CUSTUPDT
|
||||
//CUSTFILE DD DSN=PROD.CUSTOMER.MASTER,DISP=SHR
|
||||
//STEP2 EXEC PGM=RPTGEN
|
||||
//SYSOUT DD SYSOUT=*
|
||||
280
gitnexus/test/integration/resolvers/cobol.test.ts
Normal file
280
gitnexus/test/integration/resolvers/cobol.test.ts
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
/**
|
||||
* COBOL: PROGRAM-ID modules, paragraph functions, data items, COPY imports,
|
||||
* CALL cross-program resolution, EXEC SQL/CICS blocks, MOVE data flow,
|
||||
* file declarations, JCL job/step integration
|
||||
*/
|
||||
import { describe, it, expect, beforeAll } from 'vitest';
|
||||
import path from 'path';
|
||||
import {
|
||||
FIXTURES, getRelationships, getNodesByLabel,
|
||||
runPipelineFromRepo, type PipelineResult,
|
||||
} from './helpers.js';
|
||||
|
||||
describe('COBOL full system extraction', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(
|
||||
path.join(FIXTURES, 'cobol-app'),
|
||||
() => {},
|
||||
{ skipGraphPhases: true }, // COBOL is regex-based, not in SupportedLanguages enum
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
// ── Node detection ──────────────────────────────────────────────────
|
||||
|
||||
it('detects Module nodes for each PROGRAM-ID', () => {
|
||||
const modules = getNodesByLabel(result, 'Module');
|
||||
expect(modules).toContain('CUSTUPDT');
|
||||
expect(modules).toContain('AUDITLOG');
|
||||
expect(modules).toContain('RPTGEN');
|
||||
});
|
||||
|
||||
it('detects Function nodes for paragraphs', () => {
|
||||
const funcs = getNodesByLabel(result, 'Function');
|
||||
// CUSTUPDT paragraphs
|
||||
expect(funcs).toContain('MAIN-PARAGRAPH');
|
||||
expect(funcs).toContain('INIT-PARAGRAPH');
|
||||
expect(funcs).toContain('PROCESS-PARAGRAPH');
|
||||
expect(funcs).toContain('READ-CUSTOMER');
|
||||
expect(funcs).toContain('UPDATE-BALANCE');
|
||||
expect(funcs).toContain('WRITE-CUSTOMER');
|
||||
expect(funcs).toContain('CLEANUP-PARAGRAPH');
|
||||
// AUDITLOG paragraphs
|
||||
expect(funcs).toContain('WRITE-LOG');
|
||||
// RPTGEN paragraphs
|
||||
expect(funcs).toContain('FETCH-DATA');
|
||||
expect(funcs).toContain('FORMAT-REPORT');
|
||||
expect(funcs).toContain('SEND-SCREEN');
|
||||
});
|
||||
|
||||
it('detects Property nodes for data items', () => {
|
||||
const props = getNodesByLabel(result, 'Property');
|
||||
// CUSTUPDT data items
|
||||
expect(props).toContain('WS-FILE-STATUS');
|
||||
expect(props).toContain('WS-CUSTOMER-NAME');
|
||||
expect(props).toContain('WS-AMOUNT');
|
||||
expect(props).toContain('CUST-ID');
|
||||
expect(props).toContain('CUST-NAME');
|
||||
expect(props).toContain('CUST-BALANCE');
|
||||
// AUDITLOG data items
|
||||
expect(props).toContain('WS-LOG-MESSAGE');
|
||||
expect(props).toContain('WS-TIMESTAMP');
|
||||
expect(props).toContain('LS-CUST-ID');
|
||||
expect(props).toContain('LS-AMOUNT');
|
||||
// RPTGEN data items (from COPY expansion)
|
||||
expect(props).toContain('WS-REPORT-LINE');
|
||||
expect(props).toContain('WS-SQL-CODE');
|
||||
});
|
||||
|
||||
it('detects Record nodes for file declarations', () => {
|
||||
const records = getNodesByLabel(result, 'Record');
|
||||
expect(records).toContain('CUSTOMER-FILE');
|
||||
});
|
||||
|
||||
it('detects CodeElement nodes for EXEC SQL blocks', () => {
|
||||
const codeElements = getNodesByLabel(result, 'CodeElement');
|
||||
const sqlElements = codeElements.filter(n => n.startsWith('EXEC SQL'));
|
||||
expect(sqlElements.length).toBe(1);
|
||||
expect(sqlElements.some(n => n.includes('SELECT'))).toBe(true);
|
||||
});
|
||||
|
||||
it('detects CodeElement nodes for EXEC CICS blocks', () => {
|
||||
const codeElements = getNodesByLabel(result, 'CodeElement');
|
||||
const cicsElements = codeElements.filter(n => n.startsWith('EXEC CICS'));
|
||||
expect(cicsElements.length).toBe(2);
|
||||
expect(cicsElements.some(n => n.includes('SEND'))).toBe(true);
|
||||
expect(cicsElements.some(n => n.includes('LINK'))).toBe(true);
|
||||
});
|
||||
|
||||
// ── Intra-program relationships ─────────────────────────────────────
|
||||
|
||||
it('emits CALLS edges for PERFORM statements', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const performs = calls.filter(e => e.rel.reason === 'cobol-perform');
|
||||
// CUSTUPDT: MAIN performs INIT, PROCESS, CLEANUP
|
||||
expect(performs.some(e => e.target === 'INIT-PARAGRAPH')).toBe(true);
|
||||
expect(performs.some(e => e.target === 'PROCESS-PARAGRAPH')).toBe(true);
|
||||
expect(performs.some(e => e.target === 'CLEANUP-PARAGRAPH')).toBe(true);
|
||||
// PROCESS performs READ-CUSTOMER, UPDATE-BALANCE, WRITE-CUSTOMER
|
||||
expect(performs.some(e => e.target === 'READ-CUSTOMER')).toBe(true);
|
||||
expect(performs.some(e => e.target === 'UPDATE-BALANCE')).toBe(true);
|
||||
expect(performs.some(e => e.target === 'WRITE-CUSTOMER')).toBe(true);
|
||||
});
|
||||
|
||||
it('emits CALLS edges for PERFORM THRU ranges', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const thrus = calls.filter(e => e.rel.reason === 'cobol-perform-thru');
|
||||
// RPTGEN: PERFORM MAIN-PARAGRAPH THRU FORMAT-REPORT
|
||||
expect(thrus.some(e => e.target === 'FORMAT-REPORT')).toBe(true);
|
||||
});
|
||||
|
||||
it('emits CONTAINS edges for module->paragraph hierarchy', () => {
|
||||
const contains = getRelationships(result, 'CONTAINS');
|
||||
// CUSTUPDT module contains its paragraphs
|
||||
const custContains = contains.filter(e =>
|
||||
e.source === 'CUSTUPDT' && e.rel.reason === 'cobol-paragraph',
|
||||
);
|
||||
const custTargets = custContains.map(e => e.target);
|
||||
expect(custTargets).toContain('MAIN-PARAGRAPH');
|
||||
expect(custTargets).toContain('INIT-PARAGRAPH');
|
||||
expect(custTargets).toContain('PROCESS-PARAGRAPH');
|
||||
});
|
||||
|
||||
it('emits ACCESSES edges for MOVE statements (read/write)', () => {
|
||||
const accesses = getRelationships(result, 'ACCESSES');
|
||||
const moveReads = accesses.filter(e =>
|
||||
e.rel.reason === 'cobol-move-read',
|
||||
);
|
||||
const moveWrites = accesses.filter(e =>
|
||||
e.rel.reason === 'cobol-move-write',
|
||||
);
|
||||
expect(moveReads.length).toBe(3);
|
||||
expect(moveWrites.length).toBe(3);
|
||||
});
|
||||
|
||||
// ── Cross-program relationships ─────────────────────────────────────
|
||||
|
||||
it('resolves CALL to known program as CALLS edge', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const cobolCalls = calls.filter(e => e.rel.reason === 'cobol-call');
|
||||
expect(cobolCalls.length).toBe(2);
|
||||
});
|
||||
|
||||
it('emits CALLS with reason cobol-call for static CALL', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
// CUSTUPDT calls AUDITLOG
|
||||
const custToAudit = calls.filter(e =>
|
||||
e.source === 'CUSTUPDT' && e.target === 'AUDITLOG' && e.rel.reason === 'cobol-call',
|
||||
);
|
||||
expect(custToAudit.length).toBe(1);
|
||||
});
|
||||
|
||||
it('emits CALLS for EXEC CICS LINK with programName', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const cicsLinks = calls.filter(e => e.rel.reason === 'cics-link');
|
||||
expect(cicsLinks.length).toBe(1);
|
||||
// RPTGEN EXEC CICS LINK PROGRAM('AUDITLOG') — resolved in second pass
|
||||
const link = cicsLinks.find(e => e.source === 'RPTGEN');
|
||||
expect(link).toBeDefined();
|
||||
expect(link!.target).toBe('AUDITLOG');
|
||||
});
|
||||
|
||||
it('resolves CALL AUDITLOG from both CUSTUPDT and RPTGEN', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
// CUSTUPDT -> AUDITLOG via CALL (resolved in second pass)
|
||||
const custToAudit = calls.filter(e =>
|
||||
e.source === 'CUSTUPDT' && e.target === 'AUDITLOG',
|
||||
);
|
||||
expect(custToAudit.length).toBe(1);
|
||||
// RPTGEN -> AUDITLOG via EXEC CICS LINK (resolved in second pass)
|
||||
const rptToAudit = calls.filter(e =>
|
||||
e.source === 'RPTGEN' && e.target === 'AUDITLOG',
|
||||
);
|
||||
expect(rptToAudit.length).toBe(1);
|
||||
});
|
||||
|
||||
// ── COPY/import resolution ──────────────────────────────────────────
|
||||
|
||||
it('emits IMPORTS edge for COPY statement', () => {
|
||||
const imports = getRelationships(result, 'IMPORTS');
|
||||
const cobolCopies = imports.filter(e => e.rel.reason === 'cobol-copy');
|
||||
expect(cobolCopies.length).toBe(1);
|
||||
});
|
||||
|
||||
it('RPTGEN imports CUSTDAT copybook', () => {
|
||||
const imports = getRelationships(result, 'IMPORTS');
|
||||
const rptImports = imports.filter(e =>
|
||||
e.sourceFilePath.includes('RPTGEN') && e.targetFilePath.includes('CUSTDAT'),
|
||||
);
|
||||
expect(rptImports.length).toBe(1);
|
||||
expect(rptImports[0].rel.reason).toBe('cobol-copy');
|
||||
});
|
||||
|
||||
// ── EXEC SQL ────────────────────────────────────────────────────────
|
||||
|
||||
it('creates CodeElement for EXEC SQL SELECT', () => {
|
||||
const codeElements = getNodesByLabel(result, 'CodeElement');
|
||||
expect(codeElements).toContain('EXEC SQL SELECT');
|
||||
});
|
||||
|
||||
it('creates ACCESSES edge to CUSTOMER table', () => {
|
||||
const accesses = getRelationships(result, 'ACCESSES');
|
||||
// The SQL ACCESSES edge targets a synthetic Record node (<db>:CUSTOMER)
|
||||
// which is not added to the graph, so we verify by reason and source
|
||||
const sqlAccesses = accesses.filter(e =>
|
||||
e.rel.reason === 'sql-select' && e.source === 'EXEC SQL SELECT',
|
||||
);
|
||||
expect(sqlAccesses.length).toBe(1);
|
||||
});
|
||||
|
||||
// ── EXEC CICS ───────────────────────────────────────────────────────
|
||||
|
||||
it('creates CodeElement for EXEC CICS SEND MAP', () => {
|
||||
const codeElements = getNodesByLabel(result, 'CodeElement');
|
||||
// Two-word CICS command: SEND MAP is recognized as a single command
|
||||
expect(codeElements).toContain('EXEC CICS SEND MAP');
|
||||
});
|
||||
|
||||
it('creates CodeElement for EXEC CICS LINK', () => {
|
||||
const codeElements = getNodesByLabel(result, 'CodeElement');
|
||||
expect(codeElements).toContain('EXEC CICS LINK');
|
||||
});
|
||||
|
||||
// ── Data flow ───────────────────────────────────────────────────────
|
||||
|
||||
it('tracks MOVE from WS-AMOUNT to CUST-BALANCE as ACCESSES', () => {
|
||||
const accesses = getRelationships(result, 'ACCESSES');
|
||||
const writeToBalance = accesses.filter(e =>
|
||||
e.target === 'CUST-BALANCE' && e.rel.reason === 'cobol-move-write',
|
||||
);
|
||||
expect(writeToBalance.length).toBe(1);
|
||||
});
|
||||
|
||||
it('tracks MOVE from CUST-NAME to WS-CUSTOMER-NAME as ACCESSES', () => {
|
||||
const accesses = getRelationships(result, 'ACCESSES');
|
||||
const readFromName = accesses.filter(e =>
|
||||
e.target === 'CUST-NAME' && e.rel.reason === 'cobol-move-read',
|
||||
);
|
||||
const writeToWsName = accesses.filter(e =>
|
||||
e.target === 'WS-CUSTOMER-NAME' && e.rel.reason === 'cobol-move-write',
|
||||
);
|
||||
expect(readFromName.length).toBe(1);
|
||||
expect(writeToWsName.length).toBe(1);
|
||||
});
|
||||
|
||||
// ── JCL integration ─────────────────────────────────────────────────
|
||||
|
||||
it('creates CodeElement for JCL job steps', () => {
|
||||
const codeElements = getNodesByLabel(result, 'CodeElement');
|
||||
// JCL job node
|
||||
expect(codeElements).toContain('CUSTJOB');
|
||||
// JCL step nodes
|
||||
expect(codeElements).toContain('STEP1');
|
||||
expect(codeElements).toContain('STEP2');
|
||||
});
|
||||
|
||||
it('links JCL EXEC PGM=CUSTUPDT to COBOL Module', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const step1ToCust = calls.filter(e =>
|
||||
e.source === 'STEP1' && e.target === 'CUSTUPDT' && e.rel.reason === 'jcl-exec-pgm',
|
||||
);
|
||||
expect(step1ToCust.length).toBe(1);
|
||||
});
|
||||
|
||||
it('links JCL EXEC PGM=RPTGEN to COBOL Module', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const step2ToRpt = calls.filter(e =>
|
||||
e.source === 'STEP2' && e.target === 'RPTGEN' && e.rel.reason === 'jcl-exec-pgm',
|
||||
);
|
||||
expect(step2ToRpt.length).toBe(1);
|
||||
});
|
||||
|
||||
it('JCL step CALLS COBOL program', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const jclCalls = calls.filter(e => e.rel.reason === 'jcl-exec-pgm');
|
||||
expect(jclCalls.length).toBe(2);
|
||||
const targets = jclCalls.map(e => e.target).sort();
|
||||
expect(targets).toEqual(['CUSTUPDT', 'RPTGEN']);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue