From 98be3a1463319df536bdb175062703b41f1e50b6 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Thu, 16 Jan 2025 13:48:34 -0500 Subject: [PATCH 01/28] comprehensive connection tests for the connection Signed-off-by: worksofliam --- src/api/tests/a.test.ts | 10 -- src/api/tests/connection.test.ts | 290 +++++++++++++++++++++++++++++++ 2 files changed, 290 insertions(+), 10 deletions(-) delete mode 100644 src/api/tests/a.test.ts create mode 100644 src/api/tests/connection.test.ts diff --git a/src/api/tests/a.test.ts b/src/api/tests/a.test.ts deleted file mode 100644 index 2486ed7b7..000000000 --- a/src/api/tests/a.test.ts +++ /dev/null @@ -1,10 +0,0 @@ -// sum.test.js -import { expect, test } from 'vitest' -import { getConnection } from './state' -import exp from 'constants'; - -test('adds 1 + 2 to equal 3', () => { - const conn = getConnection(); - expect(conn).toBeDefined(); - expect(1+2).toBe(3); -}) \ No newline at end of file diff --git a/src/api/tests/connection.test.ts b/src/api/tests/connection.test.ts new file mode 100644 index 000000000..9869bde6a --- /dev/null +++ b/src/api/tests/connection.test.ts @@ -0,0 +1,290 @@ +// sum.test.js +import { expect, test } from 'vitest' +import { getConnection } from './state' +import { describe } from 'node:test'; +import { Tools } from '../Tools'; + +describe(`connection tests`, () => { + test('sendCommand', async () => { + const connection = getConnection(); + + const result = await connection.sendCommand({ + command: `echo "Hello world"`, + }); + + expect(result.code).toBe(0); + expect(result.stdout).toBe('Hello world'); + }) + + test('sendCommand with home directory', async () => { + const connection = getConnection(); + + const resultA = await connection.sendCommand({ + command: `pwd`, + directory: `/QSYS.LIB` + }); + + expect(resultA.code).toBe(0); + expect(resultA.stdout).toBe('/QSYS.LIB'); + + const resultB = await connection.sendCommand({ + command: `pwd`, + directory: `/home` + }); + + expect(resultB.code).toBe(0); + expect(resultB.stdout).toBe('/home'); + + const resultC = await connection.sendCommand({ + command: `pwd`, + directory: `/badnaughty` + }); + + expect(resultC.code).toBe(0); + expect(resultC.stdout).not.toBe('/badnaughty'); + }); + + test('sendCommand with environment variables', async () => { + const connection = getConnection(); + + const result = await connection.sendCommand({ + command: `echo "$vara $varB $VARC"`, + env: { + vara: `Hello`, + varB: `world`, + VARC: `cool` + } + }); + + expect(result.code).toBe(0); + expect(result.stdout).toBe('Hello world cool'); + }); + + test('getTempRemote', () => { + const connection = getConnection(); + + const fileA = connection.getTempRemote(`/some/file`); + const fileB = connection.getTempRemote(`/some/badfile`); + const fileC = connection.getTempRemote(`/some/file`); + + expect(fileA).toBe(fileC); + expect(fileA).not.toBe(fileB); + }) + + test('parseMemberPath (simple)', () => { + const connection = getConnection(); + + const memberA = connection.parserMemberPath(`/thelib/thespf/thembr.mbr`); + + expect(memberA?.asp).toBeUndefined(); + expect(memberA?.library).toBe(`THELIB`); + expect(memberA?.file).toBe(`THESPF`); + expect(memberA?.name).toBe(`THEMBR`); + expect(memberA?.extension).toBe(`MBR`); + expect(memberA?.basename).toBe(`THEMBR.MBR`); + }) + + test('parseMemberPath (ASP)', () => { + const connection = getConnection(); + + const memberA = connection.parserMemberPath(`/theasp/thelib/thespf/thembr.mbr`); + + expect(memberA?.asp).toBe(`THEASP`); + expect(memberA?.library).toBe(`THELIB`); + expect(memberA?.file).toBe(`THESPF`); + expect(memberA?.name).toBe(`THEMBR`); + expect(memberA?.extension).toBe(`MBR`); + expect(memberA?.basename).toBe(`THEMBR.MBR`); + }) + + test('parseMemberPath (no root)', () => { + const connection = getConnection(); + + const memberA = connection.parserMemberPath(`thelib/thespf/thembr.mbr`); + + expect(memberA?.asp).toBe(undefined); + expect(memberA?.library).toBe(`THELIB`); + expect(memberA?.file).toBe(`THESPF`); + expect(memberA?.name).toBe(`THEMBR`); + expect(memberA?.extension).toBe(`MBR`); + expect(memberA?.basename).toBe(`THEMBR.MBR`); + }); + + test('parseMemberPath (no extension)', () => { + const connection = getConnection(); + + const memberA = connection.parserMemberPath(`/thelib/thespf/thembr`); + + expect(memberA?.asp).toBe(undefined); + expect(memberA?.library).toBe(`THELIB`); + expect(memberA?.file).toBe(`THESPF`); + expect(memberA?.name).toBe(`THEMBR`); + expect(memberA?.extension).toBe(""); + expect(memberA?.basename).toBe(`THEMBR`); + + expect( + () => { connection.parserMemberPath(`/thelib/thespf/thembr`, true) } + ).toThrow(`Source Type extension is required.`); + }); + + test('parseMemberPath (invalid length)', () => { + const connection = getConnection(); + + expect( + () => { connection.parserMemberPath(`/thespf/thembr.mbr`) } + ).toThrow(`Invalid path: /thespf/thembr.mbr. Use format LIB/SPF/NAME.ext`); + }); + + test('runCommand (ILE)', async () => { + const connection = getConnection(); + + const result = await connection.runCommand({ + command: `DSPJOB OPTION(*DFNA)`, + environment: `ile` + }); + + expect(result?.code).toBe(0); + expect(["JOBPTY", "OUTPTY", "ENDSEV", "DDMCNV", "BRKMSG", "STSMSG", "DEVRCYACN", "TSEPOOL", "PRTKEYFMT", "SRTSEQ"].every(attribute => result.stdout.includes(attribute))).toBe(true); + }) + + test('runCommand (ILE, with error)', async () => { + const result = await getConnection().runCommand({ + command: `CHKOBJ OBJ(QSYS/NOEXIST) OBJTYPE(*DTAARA)`, + noLibList: true + }); + + expect(result?.code).not.toBe(0); + expect(result?.stderr).toBeTruthy(); + }); + + test('runCommand (ILE, custom library list)', async () => { + const connection = getConnection(); + const config = connection.getConfig(); + + const ogLibl = config!.libraryList.slice(0); + + config!.libraryList = [`QTEMP`]; + + const resultA = await connection?.runCommand({ + command: `DSPLIBL`, + environment: `ile` + }); + + config!.libraryList = ogLibl; + + expect(resultA?.code).toBe(0); + expect(resultA.stdout.includes(`QSYSINC CUR`)).toBe(false); + expect(resultA.stdout.includes(`QSYSINC USR`)).toBe(false); + + const resultB = await connection?.runCommand({ + command: `DSPLIBL`, + environment: `ile`, + env: { + '&LIBL': `QSYSINC`, + '&CURLIB': `QSYSINC` + } + }); + + expect(resultB?.code).toBe(0); + expect(resultB.stdout.includes(`QSYSINC CUR`)).toBe(true); + expect(resultB.stdout.includes(`QSYSINC USR`)).toBe(true); + }); + + test('runCommand (ILE, library list order from variable)', async () => { + const connection = getConnection(); + + const result = await connection?.runCommand({ + command: `DSPLIBL`, + environment: `ile`, + env: { + '&LIBL': `QTEMP QSYSINC`, + } + }); + + expect(result?.code).toBe(0); + + const qsysincIndex = result.stdout.indexOf(`QSYSINC USR`); + const qtempIndex = result.stdout.indexOf(`QTEMP USR`); + + // Test that QSYSINC is before QSYS2 + expect(qtempIndex < qsysincIndex).toBeTruthy(); + }); + + test('runCommand (ILE, library order from config)', async () => { + const connection = getConnection(); + const config = connection.getConfig(); + + const ogLibl = config!.libraryList.slice(0); + + config!.libraryList = [`QTEMP`, `QSYSINC`]; + + const result = await connection?.runCommand({ + command: `DSPLIBL`, + environment: `ile`, + }); + + config!.libraryList = ogLibl; + + expect(result?.code).toBe(0); + + const qsysincIndex = result.stdout.indexOf(`QSYSINC USR`); + const qtempIndex = result.stdout.indexOf(`QTEMP USR`); + + // Test that QSYSINC is before QSYS2 + expect(qtempIndex < qsysincIndex).toBeTruthy(); + }); + + test('withTempDirectory and countFiles', async () => { + const connection = getConnection(); + const content = connection.getContent()!; + let temp; + + await connection.withTempDirectory(async tempDir => { + temp = tempDir; + // Directory must exist + expect((await connection.sendCommand({ command: `[ -d ${tempDir} ]` })).code).toBe(0); + + // Directory must be empty + expect(await content.countFiles(tempDir)).toBe(0); + + const toCreate = 10; + for (let i = 0; i < toCreate; i++) { + expect((await connection.sendCommand({ command: `echo "Test ${i}" >> ${tempDir}/file${i}` })).code).toBe(0); + } + + expect(await content.countFiles(tempDir)).toBe(toCreate); + + // Directory does not exist + expect(await content.countFiles(`${tempDir}/${Tools.makeid(20)}`)).toBe(0); + }); + + if (temp) { + // Directory must be gone + expect((await connection.sendCommand({ command: `[ -d ${temp} ]` })).code).toBe(1); + } + }); + + test('upperCaseName', () => { + { + const connection = getConnection(); + const variantsBackup = connection.variantChars.local; + + try { + //CCSID 297 variants + connection.variantChars.local = '£à$'; + expect(connection.dangerousVariants).toBe(true); + expect(connection.upperCaseName("àTesT£ye$")).toBe("àTEST£YE$"); + expect(connection.upperCaseName("test_cAsE")).toBe("TEST_CASE"); + + //CCSID 37 variants + connection.variantChars.local = '#@$'; + expect(connection.dangerousVariants).toBe(false); + expect(connection.upperCaseName("@TesT#ye$")).toBe("@TEST#YE$"); + expect(connection.upperCaseName("test_cAsE")).toBe("TEST_CASE"); + } + finally { + connection.variantChars.local = variantsBackup; + } + } + }) +}) \ No newline at end of file From 9b85545eb5c62e6b88f217df4414ee93d1eb880e Mon Sep 17 00:00:00 2001 From: worksofliam Date: Thu, 16 Jan 2025 13:55:49 -0500 Subject: [PATCH 02/28] Attempt to setup CI Signed-off-by: worksofliam --- .github/workflows/test.yaml | 19 ++ src/api/tests/globalSetup.ts | 2 +- src/testing/connection.ts | 330 ----------------------------------- 3 files changed, 20 insertions(+), 331 deletions(-) create mode 100644 .github/workflows/test.yaml delete mode 100644 src/testing/connection.ts diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 000000000..fd2290097 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,19 @@ +on: + pull_request: + paths: + - 'src/api/**' + +jobs: + tester: + environment: testing_environment + name: Test runner + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + - uses: actions/setup-node@v3 + with: + node-version: 18 + - run: npm ci + - run: npm run test \ No newline at end of file diff --git a/src/api/tests/globalSetup.ts b/src/api/tests/globalSetup.ts index 9a25a96ad..0b6abb19d 100644 --- a/src/api/tests/globalSetup.ts +++ b/src/api/tests/globalSetup.ts @@ -36,7 +36,7 @@ beforeAll(async () => { // console.log(`PROGRESS: ${message}`); }, uiErrorHandler: async (connection, code, data) => { - console.log(`UI ERROR: ${code}: ${data}`); + console.log(`Connection warning: ${code}: ${JSON.stringify(data)}`); return false; }, } diff --git a/src/testing/connection.ts b/src/testing/connection.ts deleted file mode 100644 index e876543da..000000000 --- a/src/testing/connection.ts +++ /dev/null @@ -1,330 +0,0 @@ -import assert from "assert"; -import { TestSuite } from "."; -import { instance } from "../instantiate"; -import { Tools } from "../api/Tools"; - -export const ConnectionSuite: TestSuite = { - name: `Connection tests`, - tests: [ - { - name: `Test sendCommand`, test: async () => { - const connection = instance.getConnection(); - - const result = await connection?.sendCommand({ - command: `echo "Hello world"`, - }); - - assert.strictEqual(result?.code, 0); - assert.strictEqual(result?.stdout, `Hello world`); - } - }, - - { - name: `Test sendCommand home directory`, test: async () => { - const connection = instance.getConnection(); - - const resultA = await connection?.sendCommand({ - command: `pwd`, - directory: `/QSYS.LIB` - }); - - assert.strictEqual(resultA?.code, 0); - assert.strictEqual(resultA?.stdout, `/QSYS.LIB`); - - const resultB = await connection?.sendCommand({ - command: `pwd`, - directory: `/home` - }); - - assert.strictEqual(resultB?.code, 0); - assert.strictEqual(resultB?.stdout, `/home`); - - const resultC = await connection?.sendCommand({ - command: `pwd`, - directory: `/badnaughty` - }); - - assert.notStrictEqual(resultC?.stdout, `/badnaughty`); - } - }, - - { - name: `Test sendCommand with environment variables`, test: async () => { - const connection = instance.getConnection(); - - const result = await connection?.sendCommand({ - command: `echo "$vara $varB $VARC"`, - env: { - vara: `Hello`, - varB: `world`, - VARC: `cool` - } - }); - - assert.strictEqual(result?.code, 0); - assert.strictEqual(result?.stdout, `Hello world cool`); - } - }, - - { - name: `Test getTempRemote`, test: async () => { - const connection = instance.getConnection(); - - const fileA = connection?.getTempRemote(`/some/file`); - const fileB = connection?.getTempRemote(`/some/badfile`); - const fileC = connection?.getTempRemote(`/some/file`); - - assert.strictEqual(fileA, fileC); - assert.notStrictEqual(fileA, fileB); - } - }, - - { - name: `Test parserMemberPath (simple)`, test: async () => { - const connection = instance.getConnection(); - - const memberA = connection?.parserMemberPath(`/thelib/thespf/thembr.mbr`); - - assert.strictEqual(memberA?.asp, undefined); - assert.strictEqual(memberA?.library, `THELIB`); - assert.strictEqual(memberA?.file, `THESPF`); - assert.strictEqual(memberA?.name, `THEMBR`); - assert.strictEqual(memberA?.extension, `MBR`); - assert.strictEqual(memberA?.basename, `THEMBR.MBR`); - } - }, - - { - name: `Test parserMemberPath (ASP)`, test: async () => { - const connection = instance.getConnection(); - - const memberA = connection?.parserMemberPath(`/theasp/thelib/thespf/thembr.mbr`); - - assert.strictEqual(memberA?.asp, `THEASP`); - assert.strictEqual(memberA?.library, `THELIB`); - assert.strictEqual(memberA?.file, `THESPF`); - assert.strictEqual(memberA?.name, `THEMBR`); - assert.strictEqual(memberA?.extension, `MBR`); - assert.strictEqual(memberA?.basename, `THEMBR.MBR`); - } - }, - - { - name: `Test parserMemberPath (no root)`, test: async () => { - const connection = instance.getConnection(); - - const memberA = connection?.parserMemberPath(`thelib/thespf/thembr.mbr`); - - assert.strictEqual(memberA?.asp, undefined); - assert.strictEqual(memberA?.library, `THELIB`); - assert.strictEqual(memberA?.file, `THESPF`); - assert.strictEqual(memberA?.name, `THEMBR`); - assert.strictEqual(memberA?.extension, `MBR`); - assert.strictEqual(memberA?.basename, `THEMBR.MBR`); - } - }, - - { - name: `Test parserMemberPath (no extension)`, test: async () => { - const connection = instance.getConnection(); - - try { - connection?.parserMemberPath(`thelib/thespf/thembr`, true); - } catch (e: any) { - assert.strictEqual(e.message, `Source Type extension is required.`); - } - } - }, - - { - name: `Test parserMemberPath (invalid length)`, test: async () => { - const connection = instance.getConnection(); - - try { - const memberA = connection?.parserMemberPath(`/thespf/thembr.mbr`); - } catch (e: any) { - assert.strictEqual(e.message, `Invalid path: /thespf/thembr.mbr. Use format LIB/SPF/NAME.ext`); - } - } - }, - - { - name: `Test runCommand (ILE)`, test: async () => { - const connection = instance.getConnection(); - - const result = await connection!.runCommand({ - command: `DSPJOB OPTION(*DFNA)`, - environment: `ile` - }); - - assert.strictEqual(result?.code, 0); - assert.strictEqual(["JOBPTY", "OUTPTY", "ENDSEV", "DDMCNV", "BRKMSG", "STSMSG", "DEVRCYACN", "TSEPOOL", "PRTKEYFMT", "SRTSEQ"].every(attribute => result.stdout.includes(attribute)), true); - } - }, - - { - name: `Test runCommand (with error)`, test: async () => { - const connection = instance.getConnection(); - - // One day it'd be cool to test different locales/CCSIDs here - // const profileMatix = [{ccsid: 277, language: `DAN`, region: `DK`}]; - // for (const setup of profileMatix) { - // const profileChange = await connection?.runCommand({ - // command: `CHGUSRPRF USRPRF(${connection.currentUser}) CCSID(${setup.ccsid}) LANGID(${setup.language}) CNTRYID(${setup.region})`, - // noLibList: true - // }); - - // console.log(profileChange); - // assert.strictEqual(profileChange?.code, 0); - // } - - console.log((await connection?.runCommand({ command: `DSPUSRPRF USRPRF(${connection.currentUser}) OUTPUT(*PRINT)`, noLibList: true }))?.stdout); - - const result = await connection?.runCommand({ - command: `CHKOBJ OBJ(QSYS/NOEXIST) OBJTYPE(*DTAARA)`, - noLibList: true - }); - - assert.notStrictEqual(result?.code, 0); - assert.ok(result?.stderr); - } - }, - - { - name: `Test runCommand (ILE, custom libl)`, test: async () => { - const connection = instance.getConnection(); - const config = instance.getConfig(); - - const ogLibl = config!.libraryList.slice(0); - - config!.libraryList = [`QTEMP`]; - - const resultA = await connection?.runCommand({ - command: `DSPLIBL`, - environment: `ile` - }); - - config!.libraryList = ogLibl; - - assert.strictEqual(resultA?.code, 0); - assert.strictEqual(resultA.stdout.includes(`QSYSINC CUR`), false); - assert.strictEqual(resultA.stdout.includes(`QSYSINC USR`), false); - - const resultB = await connection?.runCommand({ - command: `DSPLIBL`, - environment: `ile`, - env: { - '&LIBL': `QSYSINC`, - '&CURLIB': `QSYSINC` - } - }); - - assert.strictEqual(resultB?.code, 0); - assert.strictEqual(resultB.stdout.includes(`QSYSINC CUR`), true); - assert.strictEqual(resultB.stdout.includes(`QSYSINC USR`), true); - } - }, - - { - name: `Test runCommand (ILE, libl order from variable)`, test: async () => { - const connection = instance.getConnection(); - - const result = await connection?.runCommand({ - command: `DSPLIBL`, - environment: `ile`, - env: { - '&LIBL': `QTEMP QSYSINC`, - } - }); - - assert.strictEqual(result?.code, 0); - - const qsysincIndex = result.stdout.indexOf(`QSYSINC USR`); - const qtempIndex = result.stdout.indexOf(`QTEMP USR`); - - // Test that QSYSINC is before QSYS2 - assert.ok(qtempIndex < qsysincIndex); - } - }, - - { - name: `Test runCommand (ILE, libl order from config)`, test: async () => { - const connection = instance.getConnection(); - const config = instance.getConfig(); - - const ogLibl = config!.libraryList.slice(0); - - config!.libraryList = [`QTEMP`, `QSYSINC`]; - - const result = await connection?.runCommand({ - command: `DSPLIBL`, - environment: `ile`, - }); - - config!.libraryList = ogLibl; - - assert.strictEqual(result?.code, 0); - - const qsysincIndex = result.stdout.indexOf(`QSYSINC USR`); - const qtempIndex = result.stdout.indexOf(`QTEMP USR`); - - // Test that QSYSINC is before QSYS2 - assert.ok(qtempIndex < qsysincIndex); - } - }, - { - name: `Test withTempDirectory and countFiles`, test: async () => { - const connection = instance.getConnection()!; - const content = instance.getContent()!; - let temp; - - await connection.withTempDirectory(async tempDir => { - temp = tempDir; - //Directory must exist - assert.strictEqual((await connection.sendCommand({ command: `[ -d ${tempDir} ]` })).code, 0); - - //Directory must be empty - assert.strictEqual(await content.countFiles(tempDir), 0); - - const toCreate = 10; - for (let i = 0; i < toCreate; i++) { - assert.strictEqual((await connection.sendCommand({ command: `echo "Test ${i}" >> ${tempDir}/file${i}` })).code, 0); - } - - assert.strictEqual(await content.countFiles(tempDir), toCreate); - - //Directory does not exist - assert.strictEqual(await content.countFiles(`${tempDir}/${Tools.makeid(20)}`), 0); - }); - - if (temp) { - //Directory must be gone - assert.strictEqual((await connection.sendCommand({ command: `[ -d ${temp} ]` })).code, 1); - } - } - }, - { - name: `Test upperCaseName`, test: async () => { - const connection = instance.getConnection()!; - const variantsBackup = connection.variantChars.local; - - try { - //CCSID 297 variants - connection.variantChars.local = '£à$'; - assert.strictEqual(connection.dangerousVariants, true); - assert.strictEqual(connection.upperCaseName("àTesT£ye$"), "àTEST£YE$"); - assert.strictEqual(connection.upperCaseName("test_cAsE"), "TEST_CASE"); - - //CCSID 37 variants - connection.variantChars.local = '#@$'; - assert.strictEqual(connection.dangerousVariants, false); - assert.strictEqual(connection.upperCaseName("@TesT#ye$"), "@TEST#YE$"); - assert.strictEqual(connection.upperCaseName("test_cAsE"), "TEST_CASE"); - } - finally { - connection.variantChars.local = variantsBackup; - } - } - } - ] -}; From ddbbe1bb8abfd83a036430a794a0063b330afd81 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Thu, 16 Jan 2025 13:57:56 -0500 Subject: [PATCH 03/28] Add environment variables for CI tests in workflow configuration Signed-off-by: worksofliam --- .github/workflows/test.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index fd2290097..8426c954a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -16,4 +16,9 @@ jobs: with: node-version: 18 - run: npm ci + env: + VITE_SERVER: ${{ secrets.VITE_SERVER }} + VITE_DB_USER: ${{ secrets.VITE_DB_USER }} + VITE_DB_PASS: ${{ secrets.VITE_DB_PASS }} + VITE_DB_PORT: ${{ secrets.VITE_DB_PORT }} - run: npm run test \ No newline at end of file From 7b41a8f1b2b15a5aa1c523b72b7db72c2db50975 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Thu, 16 Jan 2025 13:59:11 -0500 Subject: [PATCH 04/28] Add test command to CI workflow for improved testing coverage Signed-off-by: worksofliam --- .github/workflows/test.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 8426c954a..f87a2c379 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -16,9 +16,9 @@ jobs: with: node-version: 18 - run: npm ci + - run: npm run test env: VITE_SERVER: ${{ secrets.VITE_SERVER }} VITE_DB_USER: ${{ secrets.VITE_DB_USER }} VITE_DB_PASS: ${{ secrets.VITE_DB_PASS }} - VITE_DB_PORT: ${{ secrets.VITE_DB_PORT }} - - run: npm run test \ No newline at end of file + VITE_DB_PORT: ${{ secrets.VITE_DB_PORT }} \ No newline at end of file From ba037755926a65876318cff185ac1243c38d06a0 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Thu, 16 Jan 2025 14:10:18 -0500 Subject: [PATCH 05/28] Initial content tests Signed-off-by: worksofliam --- src/api/tests/connection.test.ts | 2 +- src/api/tests/content.test.ts | 207 +++++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 src/api/tests/content.test.ts diff --git a/src/api/tests/connection.test.ts b/src/api/tests/connection.test.ts index 9869bde6a..757999cf3 100644 --- a/src/api/tests/connection.test.ts +++ b/src/api/tests/connection.test.ts @@ -1,4 +1,4 @@ -// sum.test.js + import { expect, test } from 'vitest' import { getConnection } from './state' import { describe } from 'node:test'; diff --git a/src/api/tests/content.test.ts b/src/api/tests/content.test.ts new file mode 100644 index 000000000..d0a446827 --- /dev/null +++ b/src/api/tests/content.test.ts @@ -0,0 +1,207 @@ + +import { expect, test, describe } from 'vitest' +import { getConnection } from './state' + +describe('Content Tests', () => { + test('Test memberResolve', async () => { + const connection = getConnection(); + const content = connection.getContent(); + + const member = await content?.memberResolve(`MATH`, [ + { library: `QSYSINC`, name: `MIH` }, // Doesn't exist here + { library: `QSYSINC`, name: `H` } // Does exist + ]); + + expect(member).toEqual({ + asp: undefined, + library: `QSYSINC`, + file: `H`, + name: `MATH`, + extension: `MBR`, + basename: `MATH.MBR` + }); + }); + + test('Test memberResolve (with invalid ASP)', async () => { + const connection = getConnection(); + const content = connection.getContent(); + + const member = await content?.memberResolve(`MATH`, [ + { library: `QSYSINC`, name: `MIH` }, // Doesn't exist here + { library: `QSYSINC`, name: `H`, asp: `myasp` } // Does exist, but not in the ASP + ]); + + expect(member).toEqual({ + asp: undefined, + library: `QSYSINC`, + file: `H`, + name: `MATH`, + extension: `MBR`, + basename: `MATH.MBR` + }); + }); + + test('Test memberResolve with variants', async () => { + const connection = getConnection(); + const content = connection.getContent(); + const config = connection.getConfig(); + const tempLib = config!.tempLibrary, + tempSPF = `O_ABC`.concat(connection!.variantChars.local), + tempMbr = `O_ABC`.concat(connection!.variantChars.local); + + const result = await connection!.runCommand({ + command: `CRTSRCPF ${tempLib}/${tempSPF} MBR(${tempMbr})`, + environment: `ile` + }); + + const member = await content?.memberResolve(tempMbr, [ + { library: `QSYSINC`, name: `MIH` }, // Doesn't exist here + { library: `NOEXIST`, name: `SUP` }, // Doesn't exist here + { library: tempLib, name: tempSPF } // Does exist here + ]); + + expect(member).toEqual({ + asp: undefined, + library: tempLib, + file: tempSPF, + name: tempMbr, + extension: `MBR`, + basename: `${tempMbr}.MBR` + }); + + // Cleanup... + await connection!.runCommand({ + command: `DLTF ${tempLib}/${tempSPF}`, + environment: `ile` + }); + }); + + test('Test memberResolve with bad name', async () => { + const connection = getConnection(); + const content = connection.getContent(); + + const member = await content?.memberResolve(`BOOOP`, [ + { library: `QSYSINC`, name: `MIH` }, // Doesn't exist here + { library: `NOEXIST`, name: `SUP` }, // Doesn't exist here + { library: `QSYSINC`, name: `H` } // Doesn't exist here + ]); + + expect(member).toBeUndefined(); + }); + + test('Test objectResolve .FILE', async () => { + const connection = getConnection(); + const content = connection.getContent(); + + const lib = await content?.objectResolve(`MIH`, [ + "QSYS2", // Doesn't exist here + "QSYSINC" // Does exist + ]); + + expect(lib).toBe("QSYSINC"); + }); + + test('Test objectResolve .PGM', async () => { + const connection = getConnection(); + const content = connection.getContent(); + + const lib = await content?.objectResolve(`CMRCV`, [ + "QSYSINC", // Doesn't exist here + "QSYS2" // Does exist + ]); + + expect(lib).toBe("QSYS2"); + }); + + test('Test objectResolve .DTAARA with variants', async () => { + const connection = getConnection(); + const content = connection.getContent(); + const config = connection.getConfig(); + const tempLib = config!.tempLibrary, + tempObj = `O_ABC`.concat(connection!.variantChars.local); + + await connection!.runCommand({ + command: `CRTDTAARA ${tempLib}/${tempObj} TYPE(*CHAR)`, + environment: `ile` + }); + + const lib = await content?.objectResolve(tempObj, [ + "QSYSINC", // Doesn't exist here + "QSYS2", // Doesn't exist here + tempLib // Does exist here + ]); + + expect(lib).toBe(tempLib); + + // Cleanup... + await connection!.runCommand({ + command: `DLTDTAARA ${tempLib}/${tempObj}`, + environment: `ile` + }); + }); + + test('Test objectResolve with bad name', async () => { + const connection = getConnection(); + const content = connection.getContent(); + + const lib = await content?.objectResolve(`BOOOP`, [ + "BADLIB", // Doesn't exist here + "QSYS2", // Doesn't exist here + "QSYSINC", // Doesn't exist here + ]); + + expect(lib).toBeUndefined(); + }); + + test('Test streamfileResolve', async () => { + const content = getConnection().getContent(); + + const streamfilePath = await content?.streamfileResolve([`git`], [`/QOpenSys/pkgs/sbin`, `/QOpenSys/pkgs/bin`]); + + expect(streamfilePath).toBe(`/QOpenSys/pkgs/bin/git`); + }); + + test('Test streamfileResolve with bad name', async () => { + const content = getConnection().getContent(); + + const streamfilePath = await content?.streamfileResolve([`sup`], [`/QOpenSys/pkgs/sbin`, `/QOpenSys/pkgs/bin`]); + + expect(streamfilePath).toBeUndefined(); + }); + + test('Test streamfileResolve with multiple names', async () => { + const content = getConnection().getContent(); + + const streamfilePath = await content?.streamfileResolve([`sup`, `sup2`, `git`], [`/QOpenSys/pkgs/sbin`, `/QOpenSys/pkgs/bin`]); + + expect(streamfilePath).toBe(`/QOpenSys/pkgs/bin/git`); + }); + + test('Test streamfileResolve with blanks in names', async () => { + const connection = getConnection(); + const content = connection.getContent(); + const files = [`normalname`, `name with blank`, `name_with_quote'`, `name_with_dollar$`]; + const dir = `/tmp/${Date.now()}`; + const dirWithSubdir = `${dir}/${files[0]}`; + + let result; + + result = await connection?.sendCommand({ command: `mkdir -p "${dir}"` }); + expect(result?.code).toBe(0); + try { + for (const file of files) { + result = await connection?.sendCommand({ command: `touch "${dir}/${file}"` }); + expect(result?.code).toBe(0); + }; + + for (const file of files) { + let result = await content?.streamfileResolve([`${Date.now()}`, file], [`${Date.now()}`, dir]); + expect(result).toBe(`${dir}/${file}`); + } + } + finally { + result = await connection?.sendCommand({ command: `rm -r "${dir}"` }); + expect(result?.code).toBe(0); + } + }); +}); From cf97f2625fc432e998a9551b20d6f5e51474ec5c Mon Sep 17 00:00:00 2001 From: worksofliam Date: Thu, 16 Jan 2025 15:16:41 -0500 Subject: [PATCH 06/28] Refactor test names for consistency in content tests Signed-off-by: worksofliam --- src/api/tests/content.test.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/api/tests/content.test.ts b/src/api/tests/content.test.ts index d0a446827..014b9b2ce 100644 --- a/src/api/tests/content.test.ts +++ b/src/api/tests/content.test.ts @@ -3,7 +3,7 @@ import { expect, test, describe } from 'vitest' import { getConnection } from './state' describe('Content Tests', () => { - test('Test memberResolve', async () => { + test('memberResolve', async () => { const connection = getConnection(); const content = connection.getContent(); @@ -22,7 +22,7 @@ describe('Content Tests', () => { }); }); - test('Test memberResolve (with invalid ASP)', async () => { + test('memberResolve (with invalid ASP)', async () => { const connection = getConnection(); const content = connection.getContent(); @@ -41,7 +41,7 @@ describe('Content Tests', () => { }); }); - test('Test memberResolve with variants', async () => { + test('memberResolve with variants', async () => { const connection = getConnection(); const content = connection.getContent(); const config = connection.getConfig(); @@ -76,7 +76,7 @@ describe('Content Tests', () => { }); }); - test('Test memberResolve with bad name', async () => { + test('memberResolve with bad name', async () => { const connection = getConnection(); const content = connection.getContent(); @@ -89,7 +89,7 @@ describe('Content Tests', () => { expect(member).toBeUndefined(); }); - test('Test objectResolve .FILE', async () => { + test('objectResolve .FILE', async () => { const connection = getConnection(); const content = connection.getContent(); @@ -101,7 +101,7 @@ describe('Content Tests', () => { expect(lib).toBe("QSYSINC"); }); - test('Test objectResolve .PGM', async () => { + test('objectResolve .PGM', async () => { const connection = getConnection(); const content = connection.getContent(); @@ -113,7 +113,7 @@ describe('Content Tests', () => { expect(lib).toBe("QSYS2"); }); - test('Test objectResolve .DTAARA with variants', async () => { + test('objectResolve .DTAARA with variants', async () => { const connection = getConnection(); const content = connection.getContent(); const config = connection.getConfig(); @@ -140,7 +140,7 @@ describe('Content Tests', () => { }); }); - test('Test objectResolve with bad name', async () => { + test('objectResolve with bad name', async () => { const connection = getConnection(); const content = connection.getContent(); @@ -153,7 +153,7 @@ describe('Content Tests', () => { expect(lib).toBeUndefined(); }); - test('Test streamfileResolve', async () => { + test('streamfileResolve', async () => { const content = getConnection().getContent(); const streamfilePath = await content?.streamfileResolve([`git`], [`/QOpenSys/pkgs/sbin`, `/QOpenSys/pkgs/bin`]); @@ -161,7 +161,7 @@ describe('Content Tests', () => { expect(streamfilePath).toBe(`/QOpenSys/pkgs/bin/git`); }); - test('Test streamfileResolve with bad name', async () => { + test('streamfileResolve with bad name', async () => { const content = getConnection().getContent(); const streamfilePath = await content?.streamfileResolve([`sup`], [`/QOpenSys/pkgs/sbin`, `/QOpenSys/pkgs/bin`]); @@ -169,7 +169,7 @@ describe('Content Tests', () => { expect(streamfilePath).toBeUndefined(); }); - test('Test streamfileResolve with multiple names', async () => { + test('streamfileResolve with multiple names', async () => { const content = getConnection().getContent(); const streamfilePath = await content?.streamfileResolve([`sup`, `sup2`, `git`], [`/QOpenSys/pkgs/sbin`, `/QOpenSys/pkgs/bin`]); @@ -177,7 +177,7 @@ describe('Content Tests', () => { expect(streamfilePath).toBe(`/QOpenSys/pkgs/bin/git`); }); - test('Test streamfileResolve with blanks in names', async () => { + test('streamfileResolve with blanks in names', async () => { const connection = getConnection(); const content = connection.getContent(); const files = [`normalname`, `name with blank`, `name_with_quote'`, `name_with_dollar$`]; From f9000dc7c14d83e5aed8aee098b0bcf056795699 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Thu, 16 Jan 2025 15:40:02 -0500 Subject: [PATCH 07/28] Add missing test cases for content validation Signed-off-by: worksofliam --- src/api/tests/content.test.ts | 568 ++++++++++++++++++++++++++++++---- 1 file changed, 507 insertions(+), 61 deletions(-) diff --git a/src/api/tests/content.test.ts b/src/api/tests/content.test.ts index 014b9b2ce..63638c94a 100644 --- a/src/api/tests/content.test.ts +++ b/src/api/tests/content.test.ts @@ -1,47 +1,51 @@ -import { expect, test, describe } from 'vitest' -import { getConnection } from './state' +import { expect, describe, it } from 'vitest'; +import { getConnection } from './state'; +import util, { TextDecoder } from 'util'; +import tmp from 'tmp'; +import { Tools } from '../Tools'; +import { posix } from 'path'; describe('Content Tests', () => { - test('memberResolve', async () => { + it('memberResolve', async () => { const connection = getConnection(); const content = connection.getContent(); - const member = await content?.memberResolve(`MATH`, [ - { library: `QSYSINC`, name: `MIH` }, // Doesn't exist here - { library: `QSYSINC`, name: `H` } // Does exist + const member = await content?.memberResolve('MATH', [ + { library: 'QSYSINC', name: 'MIH' }, // Doesn't exist here + { library: 'QSYSINC', name: 'H' } // Does exist ]); expect(member).toEqual({ asp: undefined, - library: `QSYSINC`, - file: `H`, - name: `MATH`, - extension: `MBR`, - basename: `MATH.MBR` + library: 'QSYSINC', + file: 'H', + name: 'MATH', + extension: 'MBR', + basename: 'MATH.MBR' }); }); - test('memberResolve (with invalid ASP)', async () => { + it('memberResolve (with invalid ASP)', async () => { const connection = getConnection(); const content = connection.getContent(); - const member = await content?.memberResolve(`MATH`, [ - { library: `QSYSINC`, name: `MIH` }, // Doesn't exist here - { library: `QSYSINC`, name: `H`, asp: `myasp` } // Does exist, but not in the ASP + const member = await content?.memberResolve('MATH', [ + { library: 'QSYSINC', name: 'MIH' }, // Doesn't exist here + { library: 'QSYSINC', name: 'H', asp: 'myasp' } // Does exist, but not in the ASP ]); expect(member).toEqual({ asp: undefined, - library: `QSYSINC`, - file: `H`, - name: `MATH`, - extension: `MBR`, - basename: `MATH.MBR` + library: 'QSYSINC', + file: 'H', + name: 'MATH', + extension: 'MBR', + basename: 'MATH.MBR' }); }); - test('memberResolve with variants', async () => { + it('memberResolve with variants', async () => { const connection = getConnection(); const content = connection.getContent(); const config = connection.getConfig(); @@ -51,12 +55,12 @@ describe('Content Tests', () => { const result = await connection!.runCommand({ command: `CRTSRCPF ${tempLib}/${tempSPF} MBR(${tempMbr})`, - environment: `ile` + environment: 'ile' }); const member = await content?.memberResolve(tempMbr, [ - { library: `QSYSINC`, name: `MIH` }, // Doesn't exist here - { library: `NOEXIST`, name: `SUP` }, // Doesn't exist here + { library: 'QSYSINC', name: 'MIH' }, // Doesn't exist here + { library: 'NOEXIST', name: 'SUP' }, // Doesn't exist here { library: tempLib, name: tempSPF } // Does exist here ]); @@ -65,55 +69,55 @@ describe('Content Tests', () => { library: tempLib, file: tempSPF, name: tempMbr, - extension: `MBR`, + extension: 'MBR', basename: `${tempMbr}.MBR` }); // Cleanup... await connection!.runCommand({ command: `DLTF ${tempLib}/${tempSPF}`, - environment: `ile` + environment: 'ile' }); }); - test('memberResolve with bad name', async () => { + it('memberResolve with bad name', async () => { const connection = getConnection(); const content = connection.getContent(); - const member = await content?.memberResolve(`BOOOP`, [ - { library: `QSYSINC`, name: `MIH` }, // Doesn't exist here - { library: `NOEXIST`, name: `SUP` }, // Doesn't exist here - { library: `QSYSINC`, name: `H` } // Doesn't exist here + const member = await content?.memberResolve('BOOOP', [ + { library: 'QSYSINC', name: 'MIH' }, // Doesn't exist here + { library: 'NOEXIST', name: 'SUP' }, // Doesn't exist here + { library: 'QSYSINC', name: 'H' } // Doesn't exist here ]); expect(member).toBeUndefined(); }); - test('objectResolve .FILE', async () => { + it('objectResolve .FILE', async () => { const connection = getConnection(); const content = connection.getContent(); - const lib = await content?.objectResolve(`MIH`, [ - "QSYS2", // Doesn't exist here - "QSYSINC" // Does exist + const lib = await content?.objectResolve('MIH', [ + 'QSYS2', // Doesn't exist here + 'QSYSINC' // Does exist ]); - expect(lib).toBe("QSYSINC"); + expect(lib).toBe('QSYSINC'); }); - test('objectResolve .PGM', async () => { + it('objectResolve .PGM', async () => { const connection = getConnection(); const content = connection.getContent(); - const lib = await content?.objectResolve(`CMRCV`, [ - "QSYSINC", // Doesn't exist here - "QSYS2" // Does exist + const lib = await content?.objectResolve('CMRCV', [ + 'QSYSINC', // Doesn't exist here + 'QSYS2' // Does exist ]); - expect(lib).toBe("QSYS2"); + expect(lib).toBe('QSYS2'); }); - test('objectResolve .DTAARA with variants', async () => { + it('objectResolve .DTAARA with variants', async () => { const connection = getConnection(); const content = connection.getContent(); const config = connection.getConfig(); @@ -122,12 +126,12 @@ describe('Content Tests', () => { await connection!.runCommand({ command: `CRTDTAARA ${tempLib}/${tempObj} TYPE(*CHAR)`, - environment: `ile` + environment: 'ile' }); const lib = await content?.objectResolve(tempObj, [ - "QSYSINC", // Doesn't exist here - "QSYS2", // Doesn't exist here + 'QSYSINC', // Doesn't exist here + 'QSYS2', // Doesn't exist here tempLib // Does exist here ]); @@ -136,51 +140,51 @@ describe('Content Tests', () => { // Cleanup... await connection!.runCommand({ command: `DLTDTAARA ${tempLib}/${tempObj}`, - environment: `ile` + environment: 'ile' }); }); - test('objectResolve with bad name', async () => { + it('objectResolve with bad name', async () => { const connection = getConnection(); const content = connection.getContent(); - const lib = await content?.objectResolve(`BOOOP`, [ - "BADLIB", // Doesn't exist here - "QSYS2", // Doesn't exist here - "QSYSINC", // Doesn't exist here + const lib = await content?.objectResolve('BOOOP', [ + 'BADLIB', // Doesn't exist here + 'QSYS2', // Doesn't exist here + 'QSYSINC', // Doesn't exist here ]); expect(lib).toBeUndefined(); }); - test('streamfileResolve', async () => { + it('streamfileResolve', async () => { const content = getConnection().getContent(); - const streamfilePath = await content?.streamfileResolve([`git`], [`/QOpenSys/pkgs/sbin`, `/QOpenSys/pkgs/bin`]); + const streamfilePath = await content?.streamfileResolve(['git'], ['/QOpenSys/pkgs/sbin', '/QOpenSys/pkgs/bin']); - expect(streamfilePath).toBe(`/QOpenSys/pkgs/bin/git`); + expect(streamfilePath).toBe('/QOpenSys/pkgs/bin/git'); }); - test('streamfileResolve with bad name', async () => { + it('streamfileResolve with bad name', async () => { const content = getConnection().getContent(); - const streamfilePath = await content?.streamfileResolve([`sup`], [`/QOpenSys/pkgs/sbin`, `/QOpenSys/pkgs/bin`]); + const streamfilePath = await content?.streamfileResolve(['sup'], ['/QOpenSys/pkgs/sbin', '/QOpenSys/pkgs/bin']); expect(streamfilePath).toBeUndefined(); }); - test('streamfileResolve with multiple names', async () => { + it('streamfileResolve with multiple names', async () => { const content = getConnection().getContent(); - const streamfilePath = await content?.streamfileResolve([`sup`, `sup2`, `git`], [`/QOpenSys/pkgs/sbin`, `/QOpenSys/pkgs/bin`]); + const streamfilePath = await content?.streamfileResolve(['sup', 'sup2', 'git'], ['/QOpenSys/pkgs/sbin', '/QOpenSys/pkgs/bin']); - expect(streamfilePath).toBe(`/QOpenSys/pkgs/bin/git`); + expect(streamfilePath).toBe('/QOpenSys/pkgs/bin/git'); }); - test('streamfileResolve with blanks in names', async () => { + it('streamfileResolve with blanks in names', async () => { const connection = getConnection(); const content = connection.getContent(); - const files = [`normalname`, `name with blank`, `name_with_quote'`, `name_with_dollar$`]; + const files = ['normalname', 'name with blank', 'name_with_quote\'', 'name_with_dollar$']; const dir = `/tmp/${Date.now()}`; const dirWithSubdir = `${dir}/${files[0]}`; @@ -204,4 +208,446 @@ describe('Content Tests', () => { expect(result?.code).toBe(0); } }); + + it('Test downloadMemberContent', async () => { + const content = getConnection().getContent(); + + const tmpFile = await util.promisify(tmp.file)(); + const memberContent = await content?.downloadMemberContent(undefined, 'QSYSINC', 'H', 'MATH', tmpFile); + + expect(memberContent).toBeTruthy(); + }); + + it('Test runSQL (basic select)', async () => { + const connection = getConnection(); + + const rows = await connection.runSQL('select * from qiws.qcustcdt'); + expect(rows?.length).not.toBe(0); + + const firstRow = rows![0]; + expect(typeof firstRow['BALDUE']).toBe('number'); + expect(typeof firstRow['CITY']).toBe('string'); + }); + + it('Test runSQL (bad basic select)', async () => { + const connection = getConnection(); + + try { + await connection.runSQL('select from qiws.qcustcdt'); + expect.fail('Should have thrown an error'); + } catch (e: any) { + expect(e.message).toBe('Token . was not valid. Valid tokens: , FROM INTO. (42601)'); + expect(e.sqlstate).toBe('42601'); + } + }); + + it('Test runSQL (with comments)', async () => { + const connection = getConnection(); + + const rows = await connection.runSQL([ + '-- myselect', + 'select *', + 'from qiws.qcustcdt --my table', + 'limit 1', + ].join('\n')); + + expect(rows?.length).toBe(1); + }); + + it('Test getTable', async () => { + const connection = getConnection(); + const content = connection.getContent(); + + const rows = await content.getTable('qiws', 'qcustcdt', '*all'); + + expect(rows?.length).not.toBe(0); + const firstRow = rows![0]; + + expect(typeof firstRow['BALDUE']).toBe('number'); + expect(typeof firstRow['CITY']).toBe('string'); + }); + + it('Test validateLibraryList', async () => { + const content = getConnection().getContent(); + + const badLibs = await content.validateLibraryList(['SCOOBY', 'QSYSINC', 'BEEPBOOP']); + + expect(badLibs?.includes('BEEPBOOP')).toBe(true); + expect(badLibs?.includes('QSYSINC')).toBe(false); + expect(badLibs?.includes('SCOOBY')).toBe(true); + }); + + it('Test getFileList', async () => { + const content = getConnection().getContent(); + + const objects = await content?.getFileList('/'); + + const qsysLib = objects?.find(obj => obj.name === 'QSYS.LIB'); + + expect(qsysLib?.name).toBe('QSYS.LIB'); + expect(qsysLib?.path).toBe('/QSYS.LIB'); + expect(qsysLib?.type).toBe('directory'); + expect(qsysLib?.owner).toBe('qsys'); + }); + + it('Test getFileList (non-existing file)', async () => { + const content = getConnection().getContent(); + + const objects = await content?.getFileList(`/tmp/${Date.now()}`); + + expect(objects?.length).toBe(0); + }); + + it('Test getFileList (special chars)', async () => { + const connection = getConnection(); + const content = getConnection().getContent(); + const files = ['name with blank', 'name_with_quote\'', 'name_with_dollar$']; + const dir = `/tmp/${Date.now()}`; + const dirWithSubdir = `${dir}/${files[0]}`; + + let result; + + result = await connection?.sendCommand({ command: `mkdir -p "${dirWithSubdir}"` }); + expect(result?.code).toBe(0); + try { + for (const file of files) { + result = await connection?.sendCommand({ command: `touch "${dirWithSubdir}/${file}"` }); + expect(result?.code).toBe(0); + }; + + const objects = await content?.getFileList(`${dirWithSubdir}`); + expect(objects?.length).toBe(files.length); + expect(objects?.map(a => a.name).sort()).toEqual(files.sort()); + } + finally { + result = await connection?.sendCommand({ command: `rm -r "${dir}"` }); + expect(result?.code).toBe(0); + } + }); + + it('Test getObjectList (all objects)', async () => { + const content = getConnection().getContent(); + + const objects = await content?.getObjectList({ library: 'QSYSINC' }); + + expect(objects?.length).not.toBe(0); + }); + + it('Test getObjectList (pgm filter)', async () => { + const content = getConnection().getContent(); + + const objects = await content?.getObjectList({ library: 'QSYSINC', types: ['*PGM'] }); + + expect(objects?.length).not.toBe(0); + + const containsNonPgms = objects?.some(obj => obj.type !== '*PGM'); + + expect(containsNonPgms).toBe(false); + }); + + it('Test getObjectList (source files only)', async () => { + const content = getConnection().getContent(); + + const objects = await content?.getObjectList({ library: 'QSYSINC', types: ['*SRCPF'] }); + + expect(objects?.length).not.toBe(0); + + const containsNonFiles = objects?.some(obj => obj.type !== '*FILE'); + + expect(containsNonFiles).toBe(false); + }); + + it('Test getObjectList (single source file only, detailed)', async () => { + const content = getConnection().getContent(); + + const objectsA = await content?.getObjectList({ library: 'QSYSINC', types: ['*SRCPF'], object: 'MIH' }); + + expect(objectsA?.length).toBe(1); + }); + + it('Test getObjectList (source files only, named filter)', async () => { + const content = getConnection().getContent(); + + const objects = await content?.getObjectList({ library: 'QSYSINC', types: ['*SRCPF'], object: 'MIH' }); + + expect(objects?.length).toBe(1); + + expect(objects[0].type).toBe('*FILE'); + expect(objects[0].text).toBe('DATA BASE FILE FOR C INCLUDES FOR MI'); + }); + + it('getLibraries (simple filters)', async () => { + const content = getConnection().getContent(); + + const qsysLibraries = await content?.getLibraries({ library: 'QSYS*' }); + expect(qsysLibraries?.length).not.toBe(0); + expect(qsysLibraries?.every(l => l.name.startsWith('QSYS'))).toBe(true); + + const includeSYSLibraries = await content?.getLibraries({ library: '*SYS*' }); + expect(includeSYSLibraries?.length).not.toBe(0); + expect(includeSYSLibraries?.every(l => l.name.includes('SYS'))).toBe(true); + + const libraries = ['QSYSINC', 'QGPL', 'QTEMP']; + const multipleLibraries = await content?.getLibraries({ library: libraries.join(',') }); + expect(multipleLibraries?.length).toBe(libraries.length); + expect(libraries.every(l => multipleLibraries.some(o => o.name === l))).toBe(true); + }); + + it('getLibraries (regexp filters)', async () => { + const content = getConnection().getContent(); + + const qsysLibraries = await content?.getLibraries({ library: '^.*SYS[^0-9]*$', filterType: 'regex' }); + expect(qsysLibraries?.length).not.toBe(0); + expect(qsysLibraries?.every(l => /^.*SYS[^0-9]*$/.test(l.name))).toBe(true); + }); + + it('getObjectList (advanced filtering)', async () => { + const content = getConnection().getContent(); + const objects = await content?.getObjectList({ library: 'QSYSINC', object: 'L*OU*' }); + + expect(objects?.length).not.toBe(0); + expect(objects?.map(o => o.name).every(n => n.startsWith('L') && n.includes('OU'))).toBe(true); + }); + + it('getMemberList (SQL, no filter)', async () => { + const content = getConnection().getContent(); + + let members = await content?.getMemberList({ library: 'qsysinc', sourceFile: 'mih', members: '*inxen' }); + + expect(members?.length).toBe(3); + + members = await content?.getMemberList({ library: 'qsysinc', sourceFile: 'mih' }); + + const actbpgm = members?.find(mbr => mbr.name === 'ACTBPGM'); + + expect(actbpgm?.name).toBe('ACTBPGM'); + expect(actbpgm?.extension).toBe('C'); + expect(actbpgm?.text).toBe('ACTIVATE BOUND PROGRAM'); + expect(actbpgm?.library).toBe('QSYSINC'); + expect(actbpgm?.file).toBe('MIH'); + }); + + it('getMemberList (advanced filtering)', async () => { + const content = getConnection().getContent(); + + const members = await content?.getMemberList({ library: 'QSYSINC', sourceFile: 'QRPGLESRC', members: 'SYS*,I*,*EX' }); + expect(members?.length).not.toBe(0); + expect(members!.map(m => m.name).every(n => n.startsWith('SYS') || n.startsWith('I') || n.endsWith('EX'))).toBe(true); + + const membersRegex = await content?.getMemberList({ library: 'QSYSINC', sourceFile: 'QRPGLESRC', members: '^QSY(?!RTV).*$' }); + expect(membersRegex?.length).not.toBe(0); + expect(membersRegex!.map(m => m.name).every(n => n.startsWith('QSY') && !n.includes('RTV'))).toBe(true); + }); + + it('getQtempTable', async () => { + const content = getConnection().getContent(); + + const queries = [ + `CALL QSYS2.QCMDEXC('DSPOBJD OBJ(QSYSINC/*ALL) OBJTYPE(*ALL) OUTPUT(*OUTFILE) OUTFILE(QTEMP/DSPOBJD)')`, + `Create Table QTEMP.OBJECTS As ( + Select ODLBNM as LIBRARY, + ODOBNM as NAME, + ODOBAT as ATTRIBUTE, + ODOBTP as TYPE, + Coalesce(ODOBTX, '') as TEXT + From QTEMP.DSPOBJD + ) With Data` + ]; + + + const nosqlContent = await content?.getQTempTable(queries, "OBJECTS"); + const objects = nosqlContent?.map(row => ({ + library: row.LIBRARY, + name: row.NAME, + attribute: row.ATTRIBUTE, + type: row.TYPE, + text: row.TEXT, + })); + expect(objects?.length).not.toBe(0); + expect(objects?.every(obj => obj.library === "QSYSINC")).toBe(true); + + const qrpglesrc = objects?.find(obj => obj.name === "QRPGLESRC"); + expect(qrpglesrc).toBeDefined(); + expect(qrpglesrc?.attribute).toBe("PF"); + expect(qrpglesrc?.type).toBe("*FILE"); + }); + + it('toCl', () => { + const command = getConnection().getContent().toCl("TEST", { + ZERO: 0, + NONE: '*NONE', + EMPTY: `''`, + OBJNAME: `OBJECT`, + OBJCHAR: `ObJect`, + IFSPATH: `/hello/world` + }); + + expect(command).toBe("TEST ZERO(0) NONE(*NONE) EMPTY('') OBJNAME(OBJECT) OBJCHAR('ObJect') IFSPATH('/hello/world')"); + }); + + it('Check object (no exist)', async () => { + const content = getConnection().getContent(); + + const exists = await content?.checkObject({ library: 'QSYSINC', name: 'BOOOP', type: '*FILE' }); + expect(exists).toBe(false); + }); + + it('Check object (source member)', async () => { + const content = getConnection().getContent(); + + const exists = await content?.checkObject({ library: 'QSYSINC', name: 'H', type: '*FILE', member: 'MATH' }); + expect(exists).toBeTruthy(); + }); + + it('Check getMemberInfo', async () => { + const content = getConnection().getContent(); + + const memberInfoA = await content?.getMemberInfo('QSYSINC', 'H', 'MATH'); + expect(memberInfoA).toBeTruthy(); + expect(memberInfoA?.library).toBe('QSYSINC'); + expect(memberInfoA?.file).toBe('H'); + expect(memberInfoA?.name).toBe('MATH'); + expect(memberInfoA?.extension).toBe('C'); + expect(memberInfoA?.text).toBe('STANDARD HEADER FILE MATH'); + + const memberInfoB = await content?.getMemberInfo('QSYSINC', 'H', 'MEMORY'); + expect(memberInfoB).toBeTruthy(); + expect(memberInfoB?.library).toBe('QSYSINC'); + expect(memberInfoB?.file).toBe('H'); + expect(memberInfoB?.name).toBe('MEMORY'); + expect(memberInfoB?.extension).toBe('CPP'); + expect(memberInfoB?.text).toBe('C++ HEADER'); + + try { + await content?.getMemberInfo('QSYSINC', 'H', 'OH_NONO'); + } catch (error: any) { + expect(error).toBeInstanceOf(Tools.SqlError); + expect(error.sqlstate).toBe('38501'); + } + }); + + it('Test @clCommand + select statement', async () => { + const content = getConnection().getContent(); + + const [resultA] = await content.runSQL(`@CRTSAVF FILE(QTEMP/UNITTEST) TEXT('Code for i test');\nSelect * From Table(QSYS2.OBJECT_STATISTICS('QTEMP', '*FILE')) Where OBJATTRIBUTE = 'SAVF';`); + + expect(resultA.OBJNAME).toBe('UNITTEST'); + expect(resultA.OBJTEXT).toBe('Code for i test'); + + const [resultB] = await content.runStatements( + `@CRTSAVF FILE(QTEMP/UNITTEST) TEXT('Code for i test')`, + `Select * From Table(QSYS2.OBJECT_STATISTICS('QTEMP', '*FILE')) Where OBJATTRIBUTE = 'SAVF'` + ); + + expect(resultB.OBJNAME).toBe('UNITTEST'); + expect(resultB.OBJTEXT).toBe('Code for i test'); + }); + + it('should get attributes', async () => { + const connection = getConnection(); + const content = connection.getContent() + await connection.withTempDirectory(async directory => { + expect((await connection.sendCommand({ command: 'echo "I am a test file" > test.txt', directory })).code).toBe(0); + const fileAttributes = await content.getAttributes(posix.join(directory, 'test.txt'), 'DATA_SIZE', 'OBJTYPE'); + expect(fileAttributes).toBeTruthy(); + expect(fileAttributes!.OBJTYPE).toBe('*STMF'); + expect(fileAttributes!.DATA_SIZE).toBe('17'); + + const directoryAttributes = await content.getAttributes(directory, 'DATA_SIZE', 'OBJTYPE'); + expect(directoryAttributes).toBeTruthy(); + expect(directoryAttributes!.OBJTYPE).toBe('*DIR'); + expect(directoryAttributes!.DATA_SIZE).toBe('8192'); + }); + + const qsysLibraryAttributes = await content.getAttributes('/QSYS.LIB/QSYSINC.LIB', 'ASP', 'OBJTYPE'); + expect(qsysLibraryAttributes).toBeTruthy(); + expect(qsysLibraryAttributes!.OBJTYPE).toBe('*LIB'); + expect(qsysLibraryAttributes!.ASP).toBe('1'); + + const qsysFileAttributes = await content.getAttributes({ library: "QSYSINC", name: "H" }, 'ASP', 'OBJTYPE'); + expect(qsysFileAttributes).toBeTruthy(); + expect(qsysFileAttributes!.OBJTYPE).toBe('*FILE'); + expect(qsysFileAttributes!.ASP).toBe('1'); + + const qsysMemberAttributes = await content.getAttributes({ library: "QSYSINC", name: "H", member: "MATH" }, 'ASP', 'OBJTYPE'); + expect(qsysMemberAttributes).toBeTruthy(); + expect(qsysMemberAttributes!.OBJTYPE).toBe('*MBR'); + expect(qsysMemberAttributes!.ASP).toBe('1'); + }); + + it('should count members', async () => { + const connection = getConnection(); + const content = connection.getContent() + const tempLib = connection.config?.tempLibrary; + if (tempLib) { + const file = Tools.makeid(8); + const deleteSPF = async () => await connection.runCommand({ command: `DLTF FILE(${tempLib}/${file})`, noLibList: true }); + await deleteSPF(); + const createSPF = await connection.runCommand({ command: `CRTSRCPF FILE(${tempLib}/${file}) RCDLEN(112)`, noLibList: true }); + if (createSPF.code === 0) { + try { + const expectedCount = Math.floor(Math.random() * (10 - 5 + 1)) + 5; + for (let i = 0; i < expectedCount; i++) { + const createMember = await connection.runCommand({ command: `ADDPFM FILE(${tempLib}/${file}) MBR(MEMBER${i}) SRCTYPE(TXT)` }); + if (createMember.code) { + throw new Error(`Failed to create member ${tempLib}/${file},MEMBER${i}: ${createMember.stderr}`); + } + } + + const count = await content.countMembers({ library: tempLib, name: file }); + expect(count).toBe(expectedCount); + } finally { + await deleteSPF(); + } + } else { + throw new Error(`Failed to create source physical file ${tempLib}/${file}: ${createSPF.stderr}`); + } + } else { + throw new Error("No temporary library defined in configuration"); + } + }); + + it('should create streamfile', async () => { + const connection = getConnection(); + const content = connection.getContent() + await connection.withTempDirectory(async dir => { + const file = posix.join(dir, Tools.makeid()); + const fileExists = async () => content.testStreamFile(file, "f"); + expect(await fileExists()).toBe(false); + await content.createStreamFile(file); + expect(await fileExists()).toBe(true); + const attributes = await content.getAttributes(file, "CCSID"); + expect(attributes).toBeTruthy(); + expect(attributes!.CCSID).toBe("1208"); + }); + }); + + it('should handle long library name', async () => { + const connection = getConnection(); + const content = connection.getContent() + const longName = Tools.makeid(18); + const shortName = Tools.makeid(8); + const createLib = await connection.runCommand({ command: `RUNSQL 'create schema "${longName}" for ${shortName}' commit(*none)`, noLibList: true }); + if (createLib.code === 0) { + await connection.runCommand({ command: `CRTSRCPF FILE(${shortName}/SFILE) MBR(MBR) TEXT('Test long library name')` }); + + const libraries = await content.getLibraries({ library: `${shortName}` }); + expect(libraries?.length).toBe(1); + + const objects = await content.getObjectList({ library: `${shortName}`, types: [`*SRCPF`], object: `SFILE` }); + expect(objects?.length).toBe(1); + expect(objects[0].type).toBe(`*FILE`); + expect(objects[0].text).toBe(`Test long library name`); + + const memberCount = await content.countMembers({ library: `${shortName}`, name: `SFILE` }); + expect(memberCount).toBe(1); + const members = await content.getMemberList({ library: `${shortName}`, sourceFile: `SFILE` }); + + expect(members?.length).toBe(1); + + await connection.runCommand({ command: `RUNSQL 'drop schema "${longName}"' commit(*none)`, noLibList: true }); + } else { + throw new Error(`Failed to create schema "${longName}"`); + } + }); }); From b625eab3c9353fc48f6eaa4227352b0a048ce1dc Mon Sep 17 00:00:00 2001 From: worksofliam Date: Thu, 16 Jan 2025 15:41:11 -0500 Subject: [PATCH 08/28] Add additional test cases for edge scenarios in content validation Signed-off-by: worksofliam --- src/testing/content.ts | 760 +---------------------------------------- 1 file changed, 1 insertion(+), 759 deletions(-) diff --git a/src/testing/content.ts b/src/testing/content.ts index 742beac50..6cbd14d2e 100644 --- a/src/testing/content.ts +++ b/src/testing/content.ts @@ -1,242 +1,14 @@ import assert from "assert"; -import { randomInt } from "crypto"; -import { posix } from "path"; import tmp from 'tmp'; import util, { TextDecoder } from 'util'; import { Uri, workspace } from "vscode"; import { TestSuite } from "."; -import { Tools } from "../api/Tools"; import { getMemberUri } from "../filesystems/qsys/QSysFs"; import { instance } from "../instantiate"; -import { CommandResult } from "../typings"; export const ContentSuite: TestSuite = { - name: `Content API tests`, + name: `Content FileSystem API tests`, tests: [ - { - name: `Test memberResolve`, test: async () => { - const content = instance.getContent(); - - const member = await content?.memberResolve(`MATH`, [ - { library: `QSYSINC`, name: `MIH` }, // Doesn't exist here - { library: `QSYSINC`, name: `H` } // Does exist - ]); - - assert.deepStrictEqual(member, { - asp: undefined, - library: `QSYSINC`, - file: `H`, - name: `MATH`, - extension: `MBR`, - basename: `MATH.MBR` - }); - } - }, - - - { - name: `Test memberResolve (with invalid ASP)`, test: async () => { - const content = instance.getContent(); - - const member = await content?.memberResolve(`MATH`, [ - { library: `QSYSINC`, name: `MIH` }, // Doesn't exist here - { library: `QSYSINC`, name: `H`, asp: `myasp` } // Does exist, but not in the ASP - ]); - - assert.deepStrictEqual(member, { - asp: undefined, - library: `QSYSINC`, - file: `H`, - name: `MATH`, - extension: `MBR`, - basename: `MATH.MBR` - }); - } - }, - - { - name: `Test memberResolve with variants`, test: async () => { - const content = instance.getContent(); - const config = instance.getConfig(); - const connection = instance.getConnection(); - const tempLib = config!.tempLibrary, - tempSPF = `O_ABC`.concat(connection!.variantChars.local), - tempMbr = `O_ABC`.concat(connection!.variantChars.local); - - const result = await connection!.runCommand({ - command: `CRTSRCPF ${tempLib}/${tempSPF} MBR(${tempMbr})`, - environment: `ile` - }); - - const member = await content?.memberResolve(tempMbr, [ - { library: `QSYSINC`, name: `MIH` }, // Doesn't exist here - { library: `NOEXIST`, name: `SUP` }, // Doesn't exist here - { library: tempLib, name: tempSPF } // Doesn't exist here - ]); - - assert.deepStrictEqual(member, { - asp: undefined, - library: tempLib, - file: tempSPF, - name: tempMbr, - extension: `MBR`, - basename: `${tempMbr}.MBR` - }); - - // Cleanup... - await connection!.runCommand({ - command: `DLTF ${tempLib}/${tempSPF}`, - environment: `ile` - }); - } - }, - - { - name: `Test memberResolve with bad name`, test: async () => { - const content = instance.getContent(); - - const member = await content?.memberResolve(`BOOOP`, [ - { library: `QSYSINC`, name: `MIH` }, // Doesn't exist here - { library: `NOEXIST`, name: `SUP` }, // Doesn't exist here - { library: `QSYSINC`, name: `H` } // Doesn't exist here - ]); - - assert.deepStrictEqual(member, undefined); - } - }, - - { - name: `Test objectResolve .FILE`, test: async () => { - const content = instance.getContent(); - - const lib = await content?.objectResolve(`MIH`, [ - "QSYS2", // Doesn't exist here - "QSYSINC" // Does exist - ]); - - assert.strictEqual(lib, "QSYSINC"); - } - }, - - { - name: `Test objectResolve .PGM`, test: async () => { - const content = instance.getContent(); - - const lib = await content?.objectResolve(`CMRCV`, [ - "QSYSINC", // Doesn't exist here - "QSYS2" // Does exist - ]); - - assert.strictEqual(lib, "QSYS2"); - } - }, - - { - name: `Test objectResolve .DTAARA with variants`, test: async () => { - const content = instance.getContent(); - const config = instance.getConfig(); - const connection = instance.getConnection(); - const tempLib = config!.tempLibrary, - tempObj = `O_ABC`.concat(connection!.variantChars.local); - - await connection!.runCommand({ - command: `CRTDTAARA ${tempLib}/${tempObj} TYPE(*CHAR)`, - environment: `ile` - }); - - const lib = await content?.objectResolve(tempObj, [ - "QSYSINC", // Doesn't exist here - "QSYS2", // Doesn't exist here - tempLib // Does exist here - ]); - - assert.strictEqual(lib, tempLib); - - // Cleanup... - await connection!.runCommand({ - command: `DLTDTAARA ${tempLib}/${tempObj}`, - environment: `ile` - }); - } - }, - - { - name: `Test objectResolve with bad name`, test: async () => { - const content = instance.getContent(); - - const lib = await content?.objectResolve(`BOOOP`, [ - "BADLIB", // Doesn't exist here - "QSYS2", // Doesn't exist here - "QSYSINC", // Doesn't exist here - ]); - - assert.strictEqual(lib, undefined); - - } - }, - - { - name: `Test streamfileResolve`, test: async () => { - const content = instance.getContent(); - - const streamfilePath = await content?.streamfileResolve([`git`], [`/QOpenSys/pkgs/sbin`, `/QOpenSys/pkgs/bin`]) - - assert.strictEqual(streamfilePath, `/QOpenSys/pkgs/bin/git`); - } - }, - - { - name: `Test streamfileResolve with bad name`, test: async () => { - const content = instance.getContent(); - - const streamfilePath = await content?.streamfileResolve([`sup`], [`/QOpenSys/pkgs/sbin`, `/QOpenSys/pkgs/bin`]) - - assert.strictEqual(streamfilePath, undefined); - } - }, - - { - name: `Test streamfileResolve with multiple names`, test: async () => { - const content = instance.getContent(); - - const streamfilePath = await content?.streamfileResolve([`sup`, `sup2`, `git`], [`/QOpenSys/pkgs/sbin`, `/QOpenSys/pkgs/bin`]) - - assert.strictEqual(streamfilePath, `/QOpenSys/pkgs/bin/git`); - } - }, - - - - { - name: `Test streamfileResolve with blanks in names`, test: async () => { - const connection = instance.getConnection(); - const content = instance.getContent(); - const files = [`normalname`, `name with blank`, `name_with_quote'`, `name_with_dollar$`]; - const dir = `/tmp/${Date.now()}`; - const dirWithSubdir = `${dir}/${files[0]}`; - - let result: CommandResult | undefined; - - result = await connection?.sendCommand({ command: `mkdir -p "${dir}"` }); - assert.strictEqual(result?.code, 0); - try { - for (const file of files) { - result = await connection?.sendCommand({ command: `touch "${dir}/${file}"` }); - assert.strictEqual(result?.code, 0); - }; - - for (const file of files) { - let result = await content?.streamfileResolve([`${Date.now()}`, file], [`${Date.now()}`, dir]); - assert.strictEqual(result, `${dir}/${file}`, `Resolving file "${dir}/${file}" failed`); - } - } - finally { - result = await connection?.sendCommand({ command: `rm -r "${dir}"` }); - assert.strictEqual(result?.code, 0); - } - } - }, - { name: `Test downloadMemberContent`, test: async () => { const content = instance.getContent(); @@ -249,422 +21,6 @@ export const ContentSuite: TestSuite = { } }, - { - name: `Ensure source lines are correct`, test: async () => { - const connection = instance.getConnection(); - const config = instance.getConfig()!; - - assert.ok(config.enableSourceDates, `Source dates must be enabled for this test.`); - - const tempLib = config!.tempLibrary; - const file = `LINES`; - const member = `THEMEMBER`; - - await connection!.runCommand({ command: `CRTSRCPF FILE(${tempLib}/${file}) RCDLEN(112)`, noLibList: true }); - await connection!.runCommand({ command: `ADDPFM FILE(${tempLib}/${file}) MBR(${member}) SRCTYPE(TXT)`, noLibList: true }); - - const aliasName = `${tempLib}.test_${file}_${member}`; - await connection?.runSQL(`CREATE OR REPLACE ALIAS ${aliasName} for "${tempLib}"."${file}"("${member}")`); - - try { - await connection?.runSQL(`delete from ${aliasName}`); - } catch (e) { } - - const inLines = [ - `Hello world`, - `1`, - `001`, - `0002`, - `00003`, - ] - - const lines = [ - `insert into ${aliasName} (srcseq, srcdat, srcdta)`, - `values `, - inLines.map((line, index) => `(${index + 1}.00, 0, '${line}')`).join(`, `), - ]; - - await connection?.runSQL(lines.join(` `)); - - const theBadOneUri = getMemberUri({ library: tempLib, file, name: member, extension: `TXT` }); - - const memberContentBuf = await workspace.fs.readFile(theBadOneUri); - const fileContent = new TextDecoder().decode(memberContentBuf); - - const outLines = fileContent.split(`\n`); - - assert.deepStrictEqual(inLines, outLines); - } - }, - - { - name: `Test runSQL (basic select)`, test: async () => { - const content = instance.getContent(); - - const rows = await content?.runSQL(`select * from qiws.qcustcdt`); - assert.notStrictEqual(rows?.length, 0); - - const firstRow = rows![0]; - assert.strictEqual(typeof firstRow[`BALDUE`], `number`); - assert.strictEqual(typeof firstRow[`CITY`], `string`); - } - }, - - { - name: `Test runSQL (bad basic select)`, test: async () => { - const content = instance.getContent(); - - try { - await content?.runSQL(`select from qiws.qcustcdt`); - } catch (e: any) { - assert.strictEqual(e.message, `Token . was not valid. Valid tokens: , FROM INTO. (42601)`); - assert.strictEqual(e.sqlstate, `42601`); - } - } - }, - - { - name: `Test runSQL (with comments)`, test: async () => { - const content = instance.getContent(); - - const rows = await content?.runSQL([ - `-- myselect`, - `select *`, - `from qiws.qcustcdt --my table`, - `limit 1`, - ].join(`\n`)); - - assert.strictEqual(rows?.length, 1); - } - }, - - { - name: `Test getTable`, test: async () => { - const connection = instance.getConnection(); - const content = instance.getContent(); - - const rows = await content?.getTable(`qiws`, `qcustcdt`, `*all`); - - assert.notStrictEqual(rows?.length, 0); - const firstRow = rows![0]; - - assert.strictEqual(typeof firstRow[`BALDUE`], `number`); - assert.strictEqual(typeof firstRow[`CITY`], `string`); - } - }, - - { - name: `Test validateLibraryList`, test: async () => { - const content = instance.getContent(); - - const badLibs = await content?.validateLibraryList([`SCOOBY`, `QSYSINC`, `BEEPBOOP`]); - - assert.strictEqual(badLibs?.includes(`BEEPBOOP`), true); - assert.strictEqual(badLibs?.includes(`QSYSINC`), false); - assert.strictEqual(badLibs?.includes(`SCOOBY`), true); - } - }, - - { - name: `Test getFileList`, test: async () => { - const content = instance.getContent(); - - const objects = await content?.getFileList(`/`); - - const qsysLib = objects?.find(obj => obj.name === `QSYS.LIB`); - - assert.strictEqual(qsysLib?.name, `QSYS.LIB`); - assert.strictEqual(qsysLib?.path, `/QSYS.LIB`); - assert.strictEqual(qsysLib?.type, `directory`); - assert.strictEqual(qsysLib?.owner, `qsys`); - } - }, - - { - name: `Test getFileList (non-existing file)`, test: async () => { - const content = instance.getContent(); - - const objects = await content?.getFileList(`/tmp/${Date.now()}`); - - assert.strictEqual(objects?.length, 0); - } - }, - - { - name: `Test getFileList (special chars)`, test: async () => { - const connection = instance.getConnection(); - const content = instance.getContent(); - const files = [`name with blank`, `name_with_quote'`, `name_with_dollar$`]; - const dir = `/tmp/${Date.now()}`; - const dirWithSubdir = `${dir}/${files[0]}`; - - let result: CommandResult | undefined; - - result = await connection?.sendCommand({ command: `mkdir -p "${dirWithSubdir}"` }); - assert.strictEqual(result?.code, 0); - try { - for (const file of files) { - result = await connection?.sendCommand({ command: `touch "${dirWithSubdir}/${file}"` }); - assert.strictEqual(result?.code, 0); - }; - - const objects = await content?.getFileList(`${dirWithSubdir}`); - assert.strictEqual(objects?.length, files.length); - assert.deepStrictEqual(objects?.map(a => a.name).sort(), files.sort()); - } - finally { - result = await connection?.sendCommand({ command: `rm -r "${dir}"` }); - assert.strictEqual(result?.code, 0); - } - } - }, - - { - name: `Test getObjectList (all objects)`, test: async () => { - const content = instance.getContent(); - - const objects = await content?.getObjectList({ library: `QSYSINC` }); - - assert.notStrictEqual(objects?.length, 0); - } - }, - - { - name: `Test getObjectList (pgm filter)`, test: async () => { - const content = instance.getContent(); - - const objects = await content?.getObjectList({ library: `QSYSINC`, types: [`*PGM`] }); - - assert.notStrictEqual(objects?.length, 0); - - const containsNonPgms = objects?.some(obj => obj.type !== `*PGM`); - - assert.strictEqual(containsNonPgms, false); - } - }, - { - name: `Test getObjectList (source files only)`, test: async () => { - const content = instance.getContent(); - - const objects = await content?.getObjectList({ library: `QSYSINC`, types: [`*SRCPF`] }); - - assert.notStrictEqual(objects?.length, 0); - - const containsNonFiles = objects?.some(obj => obj.type !== `*FILE`); - - assert.strictEqual(containsNonFiles, false); - } - }, - { - name: `Test getObjectList (single source file only, detailed)`, test: async () => { - const content = instance.getContent(); - - const objectsA = await content?.getObjectList({ library: `QSYSINC`, types: [`*SRCPF`], object: `MIH` }); - - assert.strictEqual(objectsA?.length, 1); - } - }, - - { - name: `Test getObjectList (source files only, named filter)`, test: async () => { - const content = instance.getContent(); - - const objects = await content?.getObjectList({ library: `QSYSINC`, types: [`*SRCPF`], object: `MIH` }); - - assert.strictEqual(objects?.length, 1); - - assert.strictEqual(objects[0].type, `*FILE`); - assert.strictEqual(objects[0].text, `DATA BASE FILE FOR C INCLUDES FOR MI`); - } - }, - { - name: `getLibraries (simple filters)`, test: async () => { - const content = instance.getContent(); - - const qsysLibraries = await content?.getLibraries({ library: "QSYS*" }) - assert.notStrictEqual(qsysLibraries?.length, 0); - assert.strictEqual(qsysLibraries?.every(l => l.name.startsWith("QSYS")), true); - - const includeSYSLibraries = await content?.getLibraries({ library: "*SYS*" }); - assert.notStrictEqual(includeSYSLibraries?.length, 0); - assert.strictEqual(includeSYSLibraries?.every(l => l.name.includes("SYS")), true); - - const libraries = ["QSYSINC", "QGPL", "QTEMP"]; - const multipleLibraries = await content?.getLibraries({ library: libraries.join(",") }) - assert.strictEqual(multipleLibraries?.length, libraries.length); - assert.strictEqual(libraries.every(l => multipleLibraries.some(o => o.name === l)), true); - } - }, - { - name: `getLibraries (regexp filters)`, test: async () => { - const content = instance.getContent(); - - const qsysLibraries = await content?.getLibraries({ library: "^.*SYS[^0-9]*$", filterType: "regex" }) - assert.notStrictEqual(qsysLibraries?.length, 0); - assert.strictEqual(qsysLibraries?.every(l => /^.*SYS[^0-9]*$/.test(l.name)), true); - } - }, - { - name: `getObjectList (advanced filtering)`, test: async () => { - const content = instance.getContent(); - const objects = await content?.getObjectList({ library: `QSYSINC`, object: "L*OU*" }); - - assert.notStrictEqual(objects?.length, 0); - assert.strictEqual(objects?.map(o => o.name).every(n => n.startsWith("L") && n.includes("OU")), true); - } - }, - { - name: `getMemberList (SQL, no filter)`, test: async () => { - const content = instance.getContent(); - - let members = await content?.getMemberList({ library: `qsysinc`, sourceFile: `mih`, members: `*inxen` }); - - assert.strictEqual(members?.length, 3); - - members = await content?.getMemberList({ library: `qsysinc`, sourceFile: `mih` }); - - const actbpgm = members?.find(mbr => mbr.name === `ACTBPGM`); - - assert.strictEqual(actbpgm?.name, `ACTBPGM`); - assert.strictEqual(actbpgm?.extension, `C`); - assert.strictEqual(actbpgm?.text, `ACTIVATE BOUND PROGRAM`); - assert.strictEqual(actbpgm?.library, `QSYSINC`); - assert.strictEqual(actbpgm?.file, `MIH`); - } - }, - { - name: `getMemberList (advanced filtering)`, test: async () => { - const content = instance.getContent(); - - const members = await content?.getMemberList({ library: `QSYSINC`, sourceFile: `QRPGLESRC`, members: 'SYS*,I*,*EX' }); - assert.notStrictEqual(members?.length, 0) - assert.strictEqual(members!.map(m => m.name).every(n => n.startsWith('SYS') || n.startsWith('I') || n.endsWith('EX')), true); - - const membersRegex = await content?.getMemberList({ library: `QSYSINC`, sourceFile: `QRPGLESRC`, members: '^QSY(?!RTV).*$', filterType: "regex" }); - assert.notStrictEqual(membersRegex?.length, 0); - assert.strictEqual(membersRegex!.map(m => m.name).every(n => n.startsWith('QSY') && !n.includes('RTV')), true); - } - }, - { - name: `Test getQtempTable`, test: async () => { - const content = instance.getContent(); - - const queries = [ - `CALL QSYS2.QCMDEXC('DSPOBJD OBJ(QSYSINC/*ALL) OBJTYPE(*ALL) OUTPUT(*OUTFILE) OUTFILE(QTEMP/DSPOBJD)')`, - `Create Table QTEMP.OBJECTS As ( - Select ODLBNM as LIBRARY, - ODOBNM as NAME, - ODOBAT as ATTRIBUTE, - ODOBTP as TYPE, - Coalesce(ODOBTX, '') as TEXT - From QTEMP.DSPOBJD - ) With Data` - ]; - - - const nosqlContent = await content?.getQTempTable(queries, "OBJECTS"); - const objects = nosqlContent?.map(row => ({ - library: row.LIBRARY, - name: row.NAME, - attribute: row.ATTRIBUTE, - type: row.TYPE, - text: row.TEXT, - })); - - assert.notStrictEqual(objects?.length, 0); - assert.strictEqual(objects?.every(obj => obj.library === "QSYSINC"), true); - - const qrpglesrc = objects.find(obj => obj.name === "QRPGLESRC"); - assert.notStrictEqual(qrpglesrc, undefined); - assert.strictEqual(qrpglesrc?.attribute === "PF", true); - assert.strictEqual(qrpglesrc?.type === "*FILE", true); - }, - }, - { - name: `To CL`, test: async () => { - const command = instance.getContent()!.toCl("TEST", { - ZERO: 0, - NONE: '*NONE', - EMPTY: `''`, - OBJNAME: `OBJECT`, - OBJCHAR: `ObJect`, - IFSPATH: `/hello/world` - }); - - assert.strictEqual(command, "TEST ZERO(0) NONE(*NONE) EMPTY('') OBJNAME(OBJECT) OBJCHAR('ObJect') IFSPATH('/hello/world')"); - } - }, - { - name: `Check object (file)`, test: async () => { - const content = instance.getContent(); - - const exists = await content?.checkObject({ library: `QSYSINC`, name: `MIH`, type: `*FILE` }); - assert.ok(exists); - } - }, - { - name: `Check object (no exist)`, test: async () => { - const content = instance.getContent(); - - const exists = await content?.checkObject({ library: `QSYSINC`, name: `BOOOP`, type: `*FILE` }); - assert.strictEqual(exists, false); - } - }, - { - name: `Check object (source member)`, test: async () => { - const content = instance.getContent(); - - const exists = await content?.checkObject({ library: `QSYSINC`, name: `H`, type: `*FILE`, member: `MATH` }); - assert.ok(exists); - } - }, - { - name: `Check getMemberInfo`, test: async () => { - const content = instance.getContent(); - - const memberInfoA = await content?.getMemberInfo(`QSYSINC`, `H`, `MATH`); - assert.ok(memberInfoA); - assert.strictEqual(memberInfoA?.library === `QSYSINC`, true); - assert.strictEqual(memberInfoA?.file === `H`, true); - assert.strictEqual(memberInfoA?.name === `MATH`, true); - assert.strictEqual(memberInfoA?.extension === `C`, true); - assert.strictEqual(memberInfoA?.text === `STANDARD HEADER FILE MATH`, true); - - const memberInfoB = await content?.getMemberInfo(`QSYSINC`, `H`, `MEMORY`); - assert.ok(memberInfoB); - assert.strictEqual(memberInfoB?.library === `QSYSINC`, true); - assert.strictEqual(memberInfoB?.file === `H`, true); - assert.strictEqual(memberInfoB?.name === `MEMORY`, true); - assert.strictEqual(memberInfoB?.extension === `CPP`, true); - assert.strictEqual(memberInfoB?.text === `C++ HEADER`, true); - - try { - await content?.getMemberInfo(`QSYSINC`, `H`, `OH_NONO`) - } - catch (error: any) { - assert.ok(error instanceof Tools.SqlError); - assert.strictEqual(error.sqlstate, "38501"); - } - } - }, - { - name: `Test @clCommand + select statement`, test: async () => { - const content = instance.getContent()!; - - const [resultA] = await content.runSQL(`@CRTSAVF FILE(QTEMP/UNITTEST) TEXT('Code for i test');\nSelect * From Table(QSYS2.OBJECT_STATISTICS('QTEMP', '*FILE')) Where OBJATTRIBUTE = 'SAVF';`); - - assert.deepStrictEqual(resultA.OBJNAME, "UNITTEST"); - assert.deepStrictEqual(resultA.OBJTEXT, "Code for i test"); - - const [resultB] = await content.runStatements( - `@CRTSAVF FILE(QTEMP/UNITTEST) TEXT('Code for i test')`, - `Select * From Table(QSYS2.OBJECT_STATISTICS('QTEMP', '*FILE')) Where OBJATTRIBUTE = 'SAVF'` - ); - - assert.deepStrictEqual(resultB.OBJNAME, "UNITTEST"); - assert.deepStrictEqual(resultB.OBJTEXT, "Code for i test"); - } - }, { name: `Write tab to member using SQL`, test: async () => { // Note: This is a known failure. @@ -698,119 +54,5 @@ export const ContentSuite: TestSuite = { } }, - { - name: `Get attributes`, test: async () => { - const connection = instance.getConnection()!; - const content = instance.getContent()!; - connection.withTempDirectory(async directory => { - assert.strictEqual((await connection.sendCommand({ command: 'echo "I am a test file" > test.txt', directory })).code, 0); - const fileAttributes = await content.getAttributes(posix.join(directory, 'test.txt'), 'DATA_SIZE', 'OBJTYPE'); - assert.ok(fileAttributes); - assert.strictEqual(fileAttributes.OBJTYPE, '*STMF'); - assert.strictEqual(fileAttributes.DATA_SIZE, '17'); - - const directoryAttributes = await content.getAttributes(directory, 'DATA_SIZE', 'OBJTYPE'); - assert.ok(directoryAttributes); - assert.strictEqual(directoryAttributes.OBJTYPE, '*DIR'); - assert.strictEqual(directoryAttributes.DATA_SIZE, '8192'); - }); - - const qsysLibraryAttributes = await content.getAttributes('/QSYS.LIB/QSYSINC.LIB', 'ASP', 'OBJTYPE'); - assert.ok(qsysLibraryAttributes); - assert.strictEqual(qsysLibraryAttributes.OBJTYPE, '*LIB'); - assert.strictEqual(qsysLibraryAttributes.ASP, '1'); - - const qsysFileAttributes = await content.getAttributes({ library: "QSYSINC", name: "H" }, 'ASP', 'OBJTYPE'); - assert.ok(qsysFileAttributes); - assert.strictEqual(qsysFileAttributes.OBJTYPE, '*FILE'); - assert.strictEqual(qsysFileAttributes.ASP, '1'); - - const qsysMemberAttributes = await content.getAttributes({ library: "QSYSINC", name: "H", member: "MATH" }, 'ASP', 'OBJTYPE'); - assert.ok(qsysMemberAttributes); - assert.strictEqual(qsysMemberAttributes.OBJTYPE, '*MBR'); - assert.strictEqual(qsysMemberAttributes.ASP, '1'); - } - }, - { - name: `Test count members`, test: async () => { - const connection = instance.getConnection()!; - const content = instance.getContent()!; - const tempLib = connection.config?.tempLibrary; - if (tempLib) { - const file = Tools.makeid(8); - const deleteSPF = async () => await connection.runCommand({ command: `DLTF FILE(${tempLib}/${file})`, noLibList: true }); - await deleteSPF(); - const createSPF = await connection.runCommand({ command: `CRTSRCPF FILE(${tempLib}/${file}) RCDLEN(112)`, noLibList: true }); - if (createSPF.code === 0) { - try { - const expectedCount = randomInt(5, 10); - for (let i = 0; i < expectedCount; i++) { - const createMember = await connection!.runCommand({ command: `ADDPFM FILE(${tempLib}/${file}) MBR(MEMBER${i}) SRCTYPE(TXT)` }); - if (createMember.code) { - throw new Error(`Failed to create member ${tempLib}/${file},MEMBER${i}: ${createMember.stderr}`); - } - } - - const count = await content.countMembers({ library: tempLib, name: file }); - assert.strictEqual(count, expectedCount); - } - finally { - await deleteSPF(); - } - } - else { - throw new Error(`Failed to create source physical file ${tempLib}/${file}: ${createSPF.stderr}`); - } - } - else { - throw new Error("No temporary library defined in configuration"); - } - } - }, - { - name: "Test streamfile creation", test: async () => { - const content = instance.getContent()!; - await instance.getConnection()!.withTempDirectory(async dir => { - const file = posix.join(dir, Tools.makeid()); - const fileExists = async () => content.testStreamFile(file, "f"); - assert.strictEqual(await fileExists(), false); - await content.createStreamFile(file); - assert.strictEqual(await fileExists(), true); - const attributes = await content.getAttributes(file, "CCSID"); - assert.ok(attributes); - assert.strictEqual(attributes.CCSID, "1208"); - }); - } - }, - { - name: `Test long library name`, test: async () => { - const connection = instance.getConnection()!; - const content = instance.getContent()!; - const longName = Tools.makeid(18); - const shortName = Tools.makeid(8); - const createLib = await connection.runCommand({ command: `RUNSQL 'create schema "${longName}" for ${shortName}' commit(*none)`, noLibList: true }); - if (createLib.code === 0) { - await connection!.runCommand({ command: `CRTSRCPF FILE(${shortName}/SFILE) MBR(MBR) TEXT('Test long library name')` }); - - const libraries = await content?.getLibraries({ library: `${shortName}` }) - assert.strictEqual(libraries?.length, 1); - - const objects = await content?.getObjectList({ library: `${shortName}`, types: [`*SRCPF`], object: `SFILE` }); - assert.strictEqual(objects?.length, 1); - assert.strictEqual(objects[0].type, `*FILE`); - assert.strictEqual(objects[0].text, `Test long library name`); - - const memberCount = await content.countMembers({ library: `${shortName}`, name: `SFILE` }); - assert.strictEqual(memberCount, 1); - const members = await content?.getMemberList({ library: `${shortName}`, sourceFile: `SFILE` }); - - assert.strictEqual(members?.length, 1); - - await connection.runCommand({ command: `RUNSQL 'drop schema "${longName}"' commit(*none)`, noLibList: true }); - } else { - throw new Error(`Failed to create schema "${longName}"`); - } - } - } ] }; From bf57879387f8744ec390c18560217744b0774354 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Thu, 16 Jan 2025 15:56:28 -0500 Subject: [PATCH 09/28] Add debug and encoding tests Signed-off-by: worksofliam --- src/api/tests/debug.test.ts | 26 +++ src/api/tests/encoding.test.ts | 344 +++++++++++++++++++++++++++++++++ src/testing/debug.ts | 31 --- 3 files changed, 370 insertions(+), 31 deletions(-) create mode 100644 src/api/tests/debug.test.ts create mode 100644 src/api/tests/encoding.test.ts delete mode 100644 src/testing/debug.ts diff --git a/src/api/tests/debug.test.ts b/src/api/tests/debug.test.ts new file mode 100644 index 000000000..219dfd508 --- /dev/null +++ b/src/api/tests/debug.test.ts @@ -0,0 +1,26 @@ +import { describe, it, expect } from 'vitest'; +import { getConnection } from './state'; +import { getJavaHome } from '../configuration/DebugConfiguration'; + +describe('Debug engine tests', () => { + it('Check Java versions', async () => { + const connection = getConnection(); + + if (connection.remoteFeatures.jdk80) { + const jdk8 = getJavaHome(connection, '8'); + expect(jdk8).toBe(connection.remoteFeatures.jdk80); + } + + if (connection.remoteFeatures.jdk11) { + const jdk11 = getJavaHome(connection, '11'); + expect(jdk11).toBe(connection.remoteFeatures.jdk11); + } + + if (connection.remoteFeatures.jdk17) { + const jdk17 = getJavaHome(connection, '17'); + expect(jdk17).toBe(connection.remoteFeatures.jdk17); + } + + expect(getJavaHome(connection, '666')).toBeUndefined(); + }); +}); diff --git a/src/api/tests/encoding.test.ts b/src/api/tests/encoding.test.ts new file mode 100644 index 000000000..6f8a83afa --- /dev/null +++ b/src/api/tests/encoding.test.ts @@ -0,0 +1,344 @@ +import path from "path"; +import IBMi from "../IBMi"; +import { Tools } from "../Tools"; +import { IBMiObject } from "../types"; +import { describe, it, expect } from 'vitest'; +import { getConnection } from "./state"; + +const contents = { + '37': [`Hello world`], + '273': [`Hello world`, `àáãÄÜö£øß`], + '277': [`Hello world`, `çñßØ¢åæ`], + '297': [`Hello world`, `âÑéè¥ýÝÞã`], + '290': [`ヲッ!モトエワネチセ`, `Hello world`, `ヲッ!モトエワネチセ`], + '420': [`Hello world`, `ص ث ب`], +} + +const SHELL_CHARS = [`$`, `#`]; + +async function runCommandsWithCCSID(connection: IBMi, commands: string[], ccsid: number) { + const testPgmSrcFile = `TESTING`; + const config = connection.config!; + + const tempLib = config.tempLibrary; + const testPgmName = `T${commands.length}${ccsid}`; + const sourceFileCreated = await connection!.runCommand({ command: `CRTSRCPF FILE(${tempLib}/${testPgmSrcFile}) RCDLEN(112) CCSID(${ccsid})`, noLibList: true }); + + await connection.content.uploadMemberContent(undefined, tempLib, testPgmSrcFile, testPgmName, commands.join(`\n`)); + + const compileCommand = `CRTBNDCL PGM(${tempLib}/${testPgmName}) SRCFILE(${tempLib}/${testPgmSrcFile}) SRCMBR(${testPgmName}) REPLACE(*YES)`; + const compileResult = await connection.runCommand({ command: compileCommand, noLibList: true }); + + if (compileResult.code !== 0) { + return compileResult; + } + + const callCommand = `CALL ${tempLib}/${testPgmName}`; + const result = await connection.runCommand({ command: callCommand, noLibList: true }); + + return result; +} +describe('Encoding tests', () => { + it('Prove that input strings are messed up by CCSID', async () => { + const connection = getConnection(); + let howManyTimesItMessedUpTheResult = 0; + + for (const strCcsid in contents) { + const data = contents[strCcsid as keyof typeof contents].join(``); + + const sqlA = `select ? as THEDATA from sysibm.sysdummy1`; + const resultA = await connection?.runSQL(sqlA, { fakeBindings: [data], forceSafe: true }); + expect(resultA?.length).toBeTruthy(); + + const sqlB = `select '${data}' as THEDATA from sysibm.sysdummy1`; + const resultB = await connection?.runSQL(sqlB, { forceSafe: true }); + expect(resultB?.length).toBeTruthy(); + + expect(resultA![0].THEDATA).toBe(data); + if (resultB![0].THEDATA !== data) { + howManyTimesItMessedUpTheResult++; + } + } + + expect(howManyTimesItMessedUpTheResult).toBeTruthy(); + }); + + it('Compare Unicode to EBCDIC successfully', async () => { + const connection = getConnection(); + + const sql = `select table_name, table_owner from qsys2.systables where table_schema = ? and table_name = ?`; + const result = await connection?.runSQL(sql, { fakeBindings: [`QSYS2`, `SYSCOLUMNS`] }); + expect(result?.length).toBeTruthy(); + }); + + it('Run variants through shells', async () => { + const connection = getConnection(); + + const text = `Hello${connection?.variantChars.local}world`; + const basicCommandA = `echo "${IBMi.escapeForShell(text)}"`; + const basicCommandB = `echo '${text}'`; + const basicCommandC = `echo 'abc'\\''123'`; + const printEscapeChar = `echo "\\\\"`; + const setCommand = `set`; + + const setResult = await connection?.sendQsh({ command: setCommand }); + + const qshEscapeResult = await connection?.sendQsh({ command: printEscapeChar }); + const paseEscapeResult = await connection?.sendCommand({ command: printEscapeChar }); + + console.log(qshEscapeResult?.stdout); + console.log(paseEscapeResult?.stdout); + + const qshTextResultA = await connection?.sendQsh({ command: basicCommandA }); + const paseTextResultA = await connection?.sendCommand({ command: basicCommandA }); + + const qshTextResultB = await connection?.sendQsh({ command: basicCommandB }); + const paseTextResultB = await connection?.sendCommand({ command: basicCommandB }); + + const qshTextResultC = await connection?.sendQsh({ command: basicCommandC }); + const paseTextResultC = await connection?.sendCommand({ command: basicCommandC }); + + expect(paseEscapeResult?.stdout).toBe(`\\`); + expect(qshTextResultA?.stdout).toBe(text); + expect(paseTextResultA?.stdout).toBe(text); + expect(qshTextResultB?.stdout).toBe(text); + expect(paseTextResultB?.stdout).toBe(text); + }); + + it('streamfileResolve with dollar', async () => { + const connection = getConnection()!; + + await connection.withTempDirectory(async tempDir => { + const tempFile = path.posix.join(tempDir, `$hello`); + await connection.content.createStreamFile(tempFile); + + const resolved = await connection.content.streamfileResolve([tempFile], [`/`]); + + expect(resolved).toBe(tempFile); + }); + }); + + SHELL_CHARS.forEach(char => { + it(`Test streamfiles with shell character ${char}`, async () => { + const connection = getConnection()!; + + const nameCombos = [`${char}ABC`, `ABC${char}`, `${char}ABC${char}`, `A${char}C`]; + + await connection.withTempDirectory(async tempDir => { + for (const name of nameCombos) { + const tempFile = path.posix.join(tempDir, `${name}.txt`); + await connection.content.createStreamFile(tempFile); + + const resolved = await connection.content.streamfileResolve([tempFile], [`/`]); + expect(resolved).toBe(tempFile); + + const attributes = await connection.content.getAttributes(resolved!, `CCSID`); + expect(attributes).toBeTruthy(); + } + }); + }); + + it(`Test members with shell character ${char}`, async () => { + const connection = getConnection(); + const content = connection.getContent(); + const config = connection.getConfig() + + if (!connection.variantChars.local.includes(char)) { + return; + } + + const tempLib = config!.tempLibrary, + tempSPF = `TESTINGS`, + tempMbr = char + Tools.makeid(4); + + await connection!.runCommand({ + command: `CRTSRCPF ${tempLib}/${tempSPF} MBR(*NONE)`, + environment: `ile` + }); + + await connection!.runCommand({ + command: `ADDPFM FILE(${tempLib}/${tempSPF}) MBR(${tempMbr}) `, + environment: `ile` + }); + + const baseContent = `Hello world\r\n`; + + const attributes = await content?.getAttributes({ library: tempLib, name: tempSPF, member: tempMbr }, `CCSID`); + expect(attributes).toBeTruthy(); + + const uploadResult = await content?.uploadMemberContent(undefined, tempLib, tempSPF, tempMbr, baseContent); + expect(uploadResult).toBeTruthy(); + + const memberContentA = await content?.downloadMemberContent(undefined, tempLib, tempSPF, tempMbr); + expect(memberContentA).toBe(baseContent); + }); + }); + + it('Listing objects with variants', async () => { + const connection = getConnection(); + const content = getConnection()?.content; + if (connection && content) { + const tempLib = connection.config?.tempLibrary!; + const ccsid = connection.getCcsid(); + + let library = `TESTLIB${connection.variantChars.local}`; + let skipLibrary = false; + const sourceFile = `${connection.variantChars.local}TESTFIL`; + const dataArea = `TSTDTA${connection.variantChars.local}`; + const members: string[] = []; + + for (let i = 0; i < 5; i++) { + members.push(`TSTMBR${connection.variantChars.local}${i}`); + } + + await connection.runCommand({ command: `DLTLIB LIB(${library})`, noLibList: true }); + + const crtLib = await connection.runCommand({ command: `CRTLIB LIB(${library}) TYPE(*PROD)`, noLibList: true }); + if (Tools.parseMessages(crtLib.stderr).findId("CPD0032")) { + library = tempLib; + skipLibrary = true; + } + + let commands: string[] = []; + + commands.push(`CRTSRCPF FILE(${library}/${sourceFile}) RCDLEN(112) CCSID(${ccsid})`); + for (const member of members) { + commands.push(`ADDPFM FILE(${library}/${sourceFile}) MBR(${member}) SRCTYPE(TXT) TEXT('Test ${member}')`); + } + + commands.push(`CRTDTAARA DTAARA(${library}/${dataArea}) TYPE(*CHAR) LEN(50) VALUE('hi')`); + + const result = await runCommandsWithCCSID(connection, commands, ccsid); + expect(result.code).toBe(0); + + if (!skipLibrary) { + const [expectedLibrary] = await content.getLibraries({ library }); + expect(expectedLibrary).toBeTruthy(); + expect(library).toBe(expectedLibrary.name); + + const validated = await connection.content.validateLibraryList([tempLib, library]); + expect(validated.length).toBe(0); + + const libl = await content.getLibraryList([library]); + expect(libl.length).toBe(1); + expect(libl[0].name).toBe(library); + } + + const checkFile = (expectedObject: IBMiObject) => { + expect(expectedObject).toBeTruthy(); + expect(expectedObject.sourceFile).toBeTruthy(); + expect(expectedObject.name).toBe(sourceFile); + expect(expectedObject.library).toBe(library); + }; + + const nameFilter = await content.getObjectList({ library, types: ["*ALL"], object: `${connection.variantChars.local[0]}*` }); + expect(nameFilter.length).toBe(1); + expect(nameFilter.some(obj => obj.library === library && obj.type === `*FILE` && obj.name === sourceFile)).toBeTruthy(); + + const objectList = await content.getObjectList({ library, types: ["*ALL"] }); + expect(objectList.some(obj => obj.library === library && obj.type === `*FILE` && obj.name === sourceFile && obj.sourceFile === true)).toBeTruthy(); + expect(objectList.some(obj => obj.library === library && obj.type === `*DTAARA` && obj.name === dataArea)).toBeTruthy(); + + const expectedMembers = await content.getMemberList({ library, sourceFile }); + expect(expectedMembers).toBeTruthy(); + expect(expectedMembers.every(member => members.find(m => m === member.name && member.text?.includes(m)))).toBeTruthy(); + + const sourceFilter = await content.getObjectList({ library, types: ["*SRCPF"], object: `${connection.variantChars.local[0]}*` }); + expect(sourceFilter.length).toBe(1); + expect(sourceFilter.some(obj => obj.library === library && obj.type === `*FILE` && obj.name === sourceFile)).toBeTruthy(); + + const [expectDataArea] = await content.getObjectList({ library, object: dataArea, types: ["*DTAARA"] }); + expect(expectDataArea.name).toBe(dataArea); + expect(expectDataArea.library).toBe(library); + expect(expectDataArea.type).toBe(`*DTAARA`); + + const [expectedSourceFile] = await content.getObjectList({ library, object: sourceFile, types: ["*SRCPF"] }); + checkFile(expectedSourceFile); + } + }); + + it('Library list supports dollar sign variant', async () => { + const connection = getConnection()!; + const library = `TEST${connection.variantChars.local}LIB`; + const sourceFile = `TEST${connection.variantChars.local}FIL`; + const member = `TEST${connection.variantChars.local}MBR`; + const ccsid = connection.getCcsid(); + + if (library.includes(`$`)) { + await connection.runCommand({ command: `DLTLIB LIB(${library})`, noLibList: true }); + + const crtLib = await connection.runCommand({ command: `CRTLIB LIB(${library}) TYPE(*PROD)`, noLibList: true }); + if (Tools.parseMessages(crtLib.stderr).findId("CPD0032")) { + return; + } + + const createSourceFileCommand = await connection.runCommand({ command: `CRTSRCPF FILE(${library}/${sourceFile}) RCDLEN(112) CCSID(${ccsid})`, noLibList: true }); + expect(createSourceFileCommand.code).toBe(0); + + const addPf = await connection.runCommand({ command: `ADDPFM FILE(${library}/${sourceFile}) MBR(${member}) SRCTYPE(TXT)`, noLibList: true }); + expect(addPf.code).toBe(0); + + await connection.content.uploadMemberContent(undefined, library, sourceFile, member, [`**free`, `dsply 'Hello world';`, `return;`].join(`\n`)); + + const compileResultA = await connection.runCommand({ command: `CRTBNDRPG PGM(${library}/${member}) SRCFILE(${library}/${sourceFile}) SRCMBR(${member})`, env: { '&CURLIB': library } }); + expect(compileResultA.code).toBe(0); + + const compileResultB = await connection.runCommand({ command: `CRTBNDRPG PGM(${library}/${member}) SRCFILE(${library}/${sourceFile}) SRCMBR(${member})`, env: { '&LIBL': library } }); + expect(compileResultB.code).toBe(0); + } + }); + + it('Variant character in source names and commands', async () => { + const connection = getConnection(); + const config = connection.getConfig(); + + const ccsidData = connection.getCcsids()!; + + const tempLib = config.tempLibrary; + const varChar = connection.variantChars.local[1]; + + const testFile = `${varChar}SCOBBY`; + const testMember = `${varChar}MEMBER`; + const variantMember = `${connection.variantChars.local}MBR`; + + await connection.runCommand({ command: `DLTF FILE(${tempLib}/${testFile})`, noLibList: true }); + + const createResult = await runCommandsWithCCSID(connection, [`CRTSRCPF FILE(${tempLib}/${testFile}) RCDLEN(112) CCSID(${ccsidData.userDefaultCCSID})`], ccsidData.userDefaultCCSID); + expect(createResult.code).toBe(0); + + const addPf = await connection.runCommand({ command: `ADDPFM FILE(${tempLib}/${testFile}) MBR(${testMember}) SRCTYPE(TXT)`, noLibList: true }); + expect(addPf.code).toBe(0); + + const attributes = await connection.content.getAttributes({ library: tempLib, name: testFile, member: testMember }, `CCSID`); + expect(attributes).toBeTruthy(); + expect(attributes![`CCSID`]).toBe(String(ccsidData.userDefaultCCSID)); + + const addPfB = await connection.runCommand({ command: `ADDPFM FILE(${tempLib}/${testFile}) MBR(${variantMember}) SRCTYPE(TXT)`, noLibList: true }); + expect(addPfB.code).toBe(0); + + const attributesB = await connection.content.getAttributes({ library: tempLib, name: testFile, member: variantMember }, `CCSID`); + expect(attributesB).toBeTruthy(); + expect(attributesB![`CCSID`]).toBe(String(ccsidData.userDefaultCCSID)); + + const objects = await connection.content.getObjectList({ library: tempLib, types: [`*SRCPF`] }); + expect(objects.length).toBeTruthy(); + expect(objects.some(obj => obj.name === testFile)).toBeTruthy(); + + const members = await connection.content.getMemberList({ library: tempLib, sourceFile: testFile }); + expect(members.length).toBeTruthy(); + expect(members.some(m => m.name === testMember)).toBeTruthy(); + expect(members.some(m => m.file === testFile)).toBeTruthy(); + + const smallFilter = await connection.content.getMemberList({ library: tempLib, sourceFile: testFile, members: `${varChar}*` }); + expect(smallFilter.length).toBeTruthy(); + + const files = await connection.content.getFileList(`/QSYS.LIB/${tempLib}.LIB/${connection.sysNameInAmerican(testFile)}.FILE`); + expect(files.length).toBeTruthy(); + expect(files[0].name).toBe(connection.sysNameInAmerican(testMember) + `.MBR`); + + await connection.content.uploadMemberContent(undefined, tempLib, testFile, testMember, [`**free`, `dsply 'Hello world';`, ` `, ` `, `return;`].join(`\n`)); + + const compileResult = await connection.runCommand({ command: `CRTBNDRPG PGM(${tempLib}/${testMember}) SRCFILE(${tempLib}/${testFile}) SRCMBR(${testMember})`, noLibList: true }); + expect(compileResult.code).toBe(0); + }); +}); diff --git a/src/testing/debug.ts b/src/testing/debug.ts deleted file mode 100644 index 771fa77ca..000000000 --- a/src/testing/debug.ts +++ /dev/null @@ -1,31 +0,0 @@ -import assert from "assert"; -import { TestSuite } from "."; -import { getJavaHome } from "../api/configuration/DebugConfiguration"; -import { instance } from "../instantiate"; - -export const DebugSuite: TestSuite = { - name: `Debug engine tests`, - tests: [ - { - name: "Check Java versions", test: async () => { - const connection = instance.getConnection()!; - if(connection.remoteFeatures.jdk80){ - const jdk8 = getJavaHome(connection, '8'); - assert.strictEqual(jdk8, connection.remoteFeatures.jdk80); - } - - if(connection.remoteFeatures.jdk11){ - const jdk11 = getJavaHome(connection, '11'); - assert.strictEqual(jdk11, connection.remoteFeatures.jdk11); - } - - if(connection.remoteFeatures.jdk17){ - const jdk11 = getJavaHome(connection, '17'); - assert.strictEqual(jdk11, connection.remoteFeatures.jdk17); - } - - assert.strictEqual(getJavaHome(connection, '666'), undefined); - } - } - ] -} \ No newline at end of file From e6f2a5d472d7f3e468d7b084b82bf97616cce60f Mon Sep 17 00:00:00 2001 From: worksofliam Date: Thu, 16 Jan 2025 16:05:04 -0500 Subject: [PATCH 10/28] Register new components in global setup for testing Signed-off-by: worksofliam --- src/api/tests/globalSetup.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/api/tests/globalSetup.ts b/src/api/tests/globalSetup.ts index 0b6abb19d..80b9897d9 100644 --- a/src/api/tests/globalSetup.ts +++ b/src/api/tests/globalSetup.ts @@ -6,6 +6,12 @@ import { afterAll, beforeAll, expect } from "vitest"; import { CodeForIStorage } from "../configuration/storage/CodeForIStorage"; import { VirtualConfig } from "../configuration/config/VirtualConfig"; import { VirtualStorage } from "../configuration/storage/BaseStorage"; +import { CustomQSh } from "../components/cqsh"; +import path from "path"; +import { CopyToImport } from "../components/copyToImport"; +import { GetMemberInfo } from "../components/getMemberInfo"; +import { GetNewLibl } from "../components/getNewLibl"; +import { extensionComponentRegistry } from "../components/manager"; beforeAll(async () => { const virtualStorage = new VirtualStorage(); @@ -15,6 +21,16 @@ beforeAll(async () => { const conn = new IBMi(); + const customQsh = new CustomQSh(); + const cqshPath = path.join(__dirname, `..`, `components`, `cqsh`, `cqsh`); + customQsh.setLocalAssetPath(path.join(cqshPath)); + + const testingId = `testing`; + extensionComponentRegistry.registerComponent(testingId, customQsh); + extensionComponentRegistry.registerComponent(testingId, new GetNewLibl); + extensionComponentRegistry.registerComponent(testingId, new GetMemberInfo()); + extensionComponentRegistry.registerComponent(testingId, new CopyToImport()); + const creds = { host: ENV_CREDS.host!, name: `testsystem`, From 21472c42f0e771c85e40d8f39248ea201516aec8 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Thu, 16 Jan 2025 17:23:21 -0500 Subject: [PATCH 11/28] Enable some concurrency in the test suites Signed-off-by: worksofliam --- src/api/tests/connection.test.ts | 5 ++--- src/api/tests/content.test.ts | 2 +- src/api/tests/encoding.test.ts | 3 ++- vitest.config.ts | 3 ++- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/api/tests/connection.test.ts b/src/api/tests/connection.test.ts index 757999cf3..db79126da 100644 --- a/src/api/tests/connection.test.ts +++ b/src/api/tests/connection.test.ts @@ -1,10 +1,9 @@ -import { expect, test } from 'vitest' +import { expect, test, describe } from 'vitest' import { getConnection } from './state' -import { describe } from 'node:test'; import { Tools } from '../Tools'; -describe(`connection tests`, () => { +describe(`connection tests`, {concurrent: true}, () => { test('sendCommand', async () => { const connection = getConnection(); diff --git a/src/api/tests/content.test.ts b/src/api/tests/content.test.ts index 63638c94a..13fd98e1e 100644 --- a/src/api/tests/content.test.ts +++ b/src/api/tests/content.test.ts @@ -6,7 +6,7 @@ import tmp from 'tmp'; import { Tools } from '../Tools'; import { posix } from 'path'; -describe('Content Tests', () => { +describe('Content Tests', {concurrent: true}, () => { it('memberResolve', async () => { const connection = getConnection(); const content = connection.getContent(); diff --git a/src/api/tests/encoding.test.ts b/src/api/tests/encoding.test.ts index 6f8a83afa..5dd8ee736 100644 --- a/src/api/tests/encoding.test.ts +++ b/src/api/tests/encoding.test.ts @@ -38,6 +38,7 @@ async function runCommandsWithCCSID(connection: IBMi, commands: string[], ccsid: return result; } + describe('Encoding tests', () => { it('Prove that input strings are messed up by CCSID', async () => { const connection = getConnection(); @@ -61,7 +62,7 @@ describe('Encoding tests', () => { } expect(howManyTimesItMessedUpTheResult).toBeTruthy(); - }); + }, {timeout: 40000}); it('Compare Unicode to EBCDIC successfully', async () => { const connection = getConnection(); diff --git a/vitest.config.ts b/vitest.config.ts index c22fd1b0c..fe0530057 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -2,12 +2,13 @@ import { defineConfig } from 'vite' export default defineConfig({ - test: { + test: { // ... Specify options here. fileParallelism: false, root: './src/api', setupFiles: [ 'tests/globalSetup.ts', ], + testTimeout: 10000 }, }) \ No newline at end of file From 526d24346d3e6f40a22dc54b2d6531fe4f07f365 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Thu, 16 Jan 2025 17:23:46 -0500 Subject: [PATCH 12/28] Create JSON config stores for faster loading on second time connect Signed-off-by: worksofliam --- src/api/tests/.gitignore | 2 + src/api/tests/globalSetup.ts | 19 +++++++--- src/api/tests/testConfigSetup.ts | 64 ++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 src/api/tests/.gitignore create mode 100644 src/api/tests/testConfigSetup.ts diff --git a/src/api/tests/.gitignore b/src/api/tests/.gitignore new file mode 100644 index 000000000..fd79b3fec --- /dev/null +++ b/src/api/tests/.gitignore @@ -0,0 +1,2 @@ +config.json +storage.json \ No newline at end of file diff --git a/src/api/tests/globalSetup.ts b/src/api/tests/globalSetup.ts index 80b9897d9..3801d5a58 100644 --- a/src/api/tests/globalSetup.ts +++ b/src/api/tests/globalSetup.ts @@ -12,22 +12,29 @@ import { CopyToImport } from "../components/copyToImport"; import { GetMemberInfo } from "../components/getMemberInfo"; import { GetNewLibl } from "../components/getNewLibl"; import { extensionComponentRegistry } from "../components/manager"; +import { JsonConfig, JsonStorage } from "./testConfigSetup"; + +const testConfig = new JsonConfig(); +const testStorage = new JsonStorage(); beforeAll(async () => { - const virtualStorage = new VirtualStorage(); + const virtualStorage = testStorage; IBMi.GlobalStorage = new CodeForIStorage(virtualStorage); - IBMi.connectionManager.configMethod = new VirtualConfig(); + IBMi.connectionManager.configMethod = testConfig; + + await testStorage.load(); + await testConfig.load(); const conn = new IBMi(); const customQsh = new CustomQSh(); const cqshPath = path.join(__dirname, `..`, `components`, `cqsh`, `cqsh`); - customQsh.setLocalAssetPath(path.join(cqshPath)); + customQsh.setLocalAssetPath(cqshPath); const testingId = `testing`; extensionComponentRegistry.registerComponent(testingId, customQsh); - extensionComponentRegistry.registerComponent(testingId, new GetNewLibl); + extensionComponentRegistry.registerComponent(testingId, new GetNewLibl()); extensionComponentRegistry.registerComponent(testingId, new GetMemberInfo()); extensionComponentRegistry.registerComponent(testingId, new CopyToImport()); @@ -62,13 +69,15 @@ beforeAll(async () => { expect(result.success).toBeTruthy(); setConnection(conn); -}, 25000); +}, 10000000); afterAll(async () => { const conn = getConnection(); if (conn) { await conn.dispose(); + await testStorage.save(); + await testConfig.save(); } else { assert.fail(`Connection was not set`); } diff --git a/src/api/tests/testConfigSetup.ts b/src/api/tests/testConfigSetup.ts new file mode 100644 index 000000000..21bc2e3e2 --- /dev/null +++ b/src/api/tests/testConfigSetup.ts @@ -0,0 +1,64 @@ +import path from "path"; +import { Config } from "../configuration/config/VirtualConfig"; +import { writeFile } from "fs/promises"; +import { existsSync } from "fs"; +import { BaseStorage } from "../configuration/storage/BaseStorage"; + +const configPath = path.join(__dirname, `config.json`); +const storagePath = path.join(__dirname, `storage.json`); + +export class JsonConfig extends Config { + private readonly config: Map = new Map(); + + public async load() { + if (existsSync(configPath)) { + const data = await import(configPath); + for (const key in data) { + this.config.set(key, data[key]); + } + } + } + + public save() { + const data: any = {}; + + Array.from(this.config.keys()).forEach(key => data[key] = this.config.get(key)); + data.default = undefined; + + return writeFile(configPath, JSON.stringify(data, null, 2)); + } + + get(key: string): T | undefined { + return this.config.get(key) as T | undefined; + } + + async set(key: string, value: any): Promise { + this.config.set(key, value); + } +} + +export class JsonStorage extends BaseStorage { + protected readonly globalState: Map = new Map(); + + constructor() { + super(); + } + + public async load() { + if (existsSync(storagePath)) { + const data = await import(storagePath); + for (const key in data) { + this.globalState.set(key, data[key]); + } + } + } + + public save() { + const data: any = {}; + + Array.from(this.globalState.keys()).forEach(key => data[key] = this.globalState.get(key)); + data.default = undefined; + + return writeFile(storagePath, JSON.stringify(data, null, 2)); + } +} \ No newline at end of file From 2df56a51c29e4228b4a85934786b8ea84a71d7c8 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Thu, 16 Jan 2025 17:23:51 -0500 Subject: [PATCH 13/28] Remove unused test suites from the testing index Signed-off-by: worksofliam --- src/testing/index.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/testing/index.ts b/src/testing/index.ts index 6f5da3b1c..693f523de 100644 --- a/src/testing/index.ts +++ b/src/testing/index.ts @@ -3,9 +3,7 @@ import vscode from "vscode"; import { instance } from "../instantiate"; import { ActionSuite } from "./action"; import { ComponentSuite } from "./components"; -import { ConnectionSuite } from "./connection"; import { ContentSuite } from "./content"; -import { DebugSuite } from "./debug"; import { DeployToolsSuite } from "./deployTools"; import { EncodingSuite } from "./encoding"; import { FilterSuite } from "./filter"; @@ -18,9 +16,7 @@ import { Server } from "../typings"; const suites: TestSuite[] = [ ActionSuite, - ConnectionSuite, ContentSuite, - DebugSuite, DeployToolsSuite, ToolsSuite, ILEErrorSuite, From 6c3bad704720646c96ea99d361c469a157c2ef5e Mon Sep 17 00:00:00 2001 From: worksofliam Date: Thu, 16 Jan 2025 17:28:59 -0500 Subject: [PATCH 14/28] Move tests Signed-off-by: worksofliam --- src/api/tests/{ => suites}/connection.test.ts | 4 ++-- src/api/tests/{ => suites}/content.test.ts | 4 ++-- src/api/tests/{ => suites}/debug.test.ts | 4 ++-- src/api/tests/{ => suites}/encoding.test.ts | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) rename src/api/tests/{ => suites}/connection.test.ts (99%) rename src/api/tests/{ => suites}/content.test.ts (99%) rename src/api/tests/{ => suites}/debug.test.ts (86%) rename src/api/tests/{ => suites}/encoding.test.ts (99%) diff --git a/src/api/tests/connection.test.ts b/src/api/tests/suites/connection.test.ts similarity index 99% rename from src/api/tests/connection.test.ts rename to src/api/tests/suites/connection.test.ts index db79126da..6d4e5a9a2 100644 --- a/src/api/tests/connection.test.ts +++ b/src/api/tests/suites/connection.test.ts @@ -1,7 +1,7 @@ import { expect, test, describe } from 'vitest' -import { getConnection } from './state' -import { Tools } from '../Tools'; +import { getConnection } from '../state' +import { Tools } from '../../Tools'; describe(`connection tests`, {concurrent: true}, () => { test('sendCommand', async () => { diff --git a/src/api/tests/content.test.ts b/src/api/tests/suites/content.test.ts similarity index 99% rename from src/api/tests/content.test.ts rename to src/api/tests/suites/content.test.ts index 13fd98e1e..8c02bce01 100644 --- a/src/api/tests/content.test.ts +++ b/src/api/tests/suites/content.test.ts @@ -1,9 +1,9 @@ import { expect, describe, it } from 'vitest'; -import { getConnection } from './state'; +import { getConnection } from '../state'; import util, { TextDecoder } from 'util'; import tmp from 'tmp'; -import { Tools } from '../Tools'; +import { Tools } from '../../Tools'; import { posix } from 'path'; describe('Content Tests', {concurrent: true}, () => { diff --git a/src/api/tests/debug.test.ts b/src/api/tests/suites/debug.test.ts similarity index 86% rename from src/api/tests/debug.test.ts rename to src/api/tests/suites/debug.test.ts index 219dfd508..d5bb528cc 100644 --- a/src/api/tests/debug.test.ts +++ b/src/api/tests/suites/debug.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; -import { getConnection } from './state'; -import { getJavaHome } from '../configuration/DebugConfiguration'; +import { getConnection } from '../state'; +import { getJavaHome } from '../../configuration/DebugConfiguration'; describe('Debug engine tests', () => { it('Check Java versions', async () => { diff --git a/src/api/tests/encoding.test.ts b/src/api/tests/suites/encoding.test.ts similarity index 99% rename from src/api/tests/encoding.test.ts rename to src/api/tests/suites/encoding.test.ts index 5dd8ee736..e20c1df9c 100644 --- a/src/api/tests/encoding.test.ts +++ b/src/api/tests/suites/encoding.test.ts @@ -1,9 +1,9 @@ import path from "path"; -import IBMi from "../IBMi"; -import { Tools } from "../Tools"; -import { IBMiObject } from "../types"; +import IBMi from "../../IBMi"; +import { Tools } from "../../Tools"; +import { IBMiObject } from "../../types"; import { describe, it, expect } from 'vitest'; -import { getConnection } from "./state"; +import { getConnection } from "../state"; const contents = { '37': [`Hello world`], From f724d3fd1274bc2cb2ace9a1d100b887a89cfccc Mon Sep 17 00:00:00 2001 From: worksofliam Date: Thu, 16 Jan 2025 17:30:57 -0500 Subject: [PATCH 15/28] Remove backend test cases from encodings Signed-off-by: worksofliam --- src/testing/encoding.ts | 208 ---------------------------------------- 1 file changed, 208 deletions(-) diff --git a/src/testing/encoding.ts b/src/testing/encoding.ts index bd486ad35..0eebbf109 100644 --- a/src/testing/encoding.ts +++ b/src/testing/encoding.ts @@ -1,12 +1,10 @@ import assert from "assert"; -import os from "os"; import { Uri, workspace } from "vscode"; import { TestSuite } from "."; import IBMi from "../api/IBMi"; import { Tools } from "../api/Tools"; import { getMemberUri } from "../filesystems/qsys/QSysFs"; import { instance } from "../instantiate"; -import { IBMiObject } from "../typings"; import path from "path"; const contents = { @@ -56,41 +54,6 @@ export const EncodingSuite: TestSuite = { }, tests: [ - { - name: `Prove that input strings are messed up by CCSID`, test: async () => { - const connection = instance.getConnection(); - let howManyTimesItMessedUpTheResult = 0; - - for (const strCcsid in contents) { - const data = contents[strCcsid as keyof typeof contents].join(``); - - // Note that it always works with the buffer! - const sqlA = `select ? as THEDATA from sysibm.sysdummy1`; - const resultA = await connection?.runSQL(sqlA, { fakeBindings: [data], forceSafe: true }); - assert.ok(resultA?.length); - - const sqlB = `select '${data}' as THEDATA from sysibm.sysdummy1`; - const resultB = await connection?.runSQL(sqlB, { forceSafe: true }); - assert.ok(resultB?.length); - - assert.strictEqual(resultA![0].THEDATA, data); - if (resultB![0].THEDATA !== data) { - howManyTimesItMessedUpTheResult++; - } - } - - assert.ok(howManyTimesItMessedUpTheResult); - } - }, - { - name: `Compare Unicode to EBCDIC successfully`, test: async () => { - const connection = instance.getConnection(); - - const sql = `select table_name, table_owner from qsys2.systables where table_schema = ? and table_name = ?`; - const result = await connection?.runSQL(sql, { fakeBindings: [`QSYS2`, `SYSCOLUMNS`] }); - assert.ok(result?.length); - } - }, { name: `Files and directories with spaces`, test: async () => { const connection = instance.getConnection()!; @@ -129,55 +92,6 @@ export const EncodingSuite: TestSuite = { }); } }, - { - name: `Run variants through shells`, test: async () => { - const connection = instance.getConnection(); - - const text = `Hello${connection?.variantChars.local}world`; - const basicCommandA = `echo "${IBMi.escapeForShell(text)}"`; - const basicCommandB = `echo '${text}'`; - const basicCommandC = `echo 'abc'\\''123'`; - const printEscapeChar = `echo "\\\\"`; - const setCommand = `set`; - - const setResult = await connection?.sendQsh({ command: setCommand }); - - const qshEscapeResult = await connection?.sendQsh({ command: printEscapeChar }); - const paseEscapeResult = await connection?.sendCommand({ command: printEscapeChar }); - - console.log(qshEscapeResult?.stdout); - console.log(paseEscapeResult?.stdout); - - const qshTextResultA = await connection?.sendQsh({ command: basicCommandA }); - const paseTextResultA = await connection?.sendCommand({ command: basicCommandA }); - - const qshTextResultB = await connection?.sendQsh({ command: basicCommandB }); - const paseTextResultB = await connection?.sendCommand({ command: basicCommandB }); - - const qshTextResultC = await connection?.sendQsh({ command: basicCommandC }); - const paseTextResultC = await connection?.sendCommand({ command: basicCommandC }); - - assert.strictEqual(paseEscapeResult?.stdout, `\\`); - assert.strictEqual(qshTextResultA?.stdout, text); - assert.strictEqual(paseTextResultA?.stdout, text); - assert.strictEqual(qshTextResultB?.stdout, text); - assert.strictEqual(paseTextResultB?.stdout, text); - } - }, - { - name: `streamfileResolve with dollar`, test: async () => { - const connection = instance.getConnection()!; - - await connection.withTempDirectory(async tempDir => { - const tempFile = path.posix.join(tempDir, `$hello`); - await connection.content.createStreamFile(tempFile); - - const resolved = await connection.content.streamfileResolve([tempFile], [`/`]); - - assert.strictEqual(resolved, tempFile); - }); - } - }, ...SHELL_CHARS.map(char => ({ name: `Test streamfiles with shell character ${char}`, test: async () => { const connection = instance.getConnection()!; @@ -256,128 +170,6 @@ export const EncodingSuite: TestSuite = { assert.ok(fileContent.includes(`Woah`)); } })), - { - name: "Listing objects with variants", - test: async () => { - const connection = instance.getConnection(); - const content = instance.getConnection()?.content; - if (connection && content) { - const tempLib = connection.config?.tempLibrary!; - const ccsid = connection.getCcsid(); - - let library = `TESTLIB${connection.variantChars.local}`; - let skipLibrary = false; - const sourceFile = `${connection.variantChars.local}TESTFIL`; - const dataArea = `TSTDTA${connection.variantChars.local}`; - const members: string[] = []; - - for (let i = 0; i < 5; i++) { - members.push(`TSTMBR${connection.variantChars.local}${i}`); - } - - await connection.runCommand({ command: `DLTLIB LIB(${library})`, noLibList: true }); - - const crtLib = await connection.runCommand({ command: `CRTLIB LIB(${library}) TYPE(*PROD)`, noLibList: true }); - if (Tools.parseMessages(crtLib.stderr).findId("CPD0032")) { - //Not authorized: carry on, skip library name test - library = tempLib; - skipLibrary = true - } - - let commands: string[] = []; - - commands.push(`CRTSRCPF FILE(${library}/${sourceFile}) RCDLEN(112) CCSID(${ccsid})`); - for (const member of members) { - commands.push(`ADDPFM FILE(${library}/${sourceFile}) MBR(${member}) SRCTYPE(TXT) TEXT('Test ${member}')`); - } - - commands.push(`CRTDTAARA DTAARA(${library}/${dataArea}) TYPE(*CHAR) LEN(50) VALUE('hi')`); - - // runCommandsWithCCSID proves that using variant characters in runCommand works! - const result = await runCommandsWithCCSID(connection, commands, ccsid); - assert.strictEqual(result.code, 0); - - if (!skipLibrary) { - const [expectedLibrary] = await content.getLibraries({ library }); - assert.ok(expectedLibrary); - assert.strictEqual(library, expectedLibrary.name); - - const validated = await connection.content.validateLibraryList([tempLib, library]); - assert.strictEqual(validated.length, 0); - - const libl = await content.getLibraryList([library]); - assert.strictEqual(libl.length, 1); - assert.strictEqual(libl[0].name, library); - } - - const checkFile = (expectedObject: IBMiObject) => { - assert.ok(expectedObject); - assert.ok(expectedObject.sourceFile, `${expectedObject.name} not a source file`); - assert.strictEqual(expectedObject.name, sourceFile); - assert.strictEqual(expectedObject.library, library); - }; - - const nameFilter = await content.getObjectList({ library, types: ["*ALL"], object: `${connection.variantChars.local[0]}*` }); - assert.strictEqual(nameFilter.length, 1); - assert.ok(nameFilter.some(obj => obj.library === library && obj.type === `*FILE` && obj.name === sourceFile)); - - const objectList = await content.getObjectList({ library, types: ["*ALL"] }); - assert.ok(objectList.some(obj => obj.library === library && obj.type === `*FILE` && obj.name === sourceFile && obj.sourceFile === true)); - assert.ok(objectList.some(obj => obj.library === library && obj.type === `*DTAARA` && obj.name === dataArea)); - - const expectedMembers = await content.getMemberList({ library, sourceFile }); - assert.ok(expectedMembers); - assert.ok(expectedMembers.every(member => members.find(m => m === member.name && member.text?.includes(m)))); - - const sourceFilter = await content.getObjectList({ library, types: ["*SRCPF"], object: `${connection.variantChars.local[0]}*` }); - assert.strictEqual(sourceFilter.length, 1); - assert.ok(sourceFilter.some(obj => obj.library === library && obj.type === `*FILE` && obj.name === sourceFile)); - - const [expectDataArea] = await content.getObjectList({ library, object: dataArea, types: ["*DTAARA"] }); - assert.strictEqual(expectDataArea.name, dataArea); - assert.strictEqual(expectDataArea.library, library); - assert.strictEqual(expectDataArea.type, `*DTAARA`); - - const [expectedSourceFile] = await content.getObjectList({ library, object: sourceFile, types: ["*SRCPF"] }); - checkFile(expectedSourceFile); - - } - } - }, - { - name: `Library list supports dollar sign variant`, test: async () => { - const connection = instance.getConnection()!; - const library = `TEST${connection.variantChars.local}LIB`; - const sourceFile = `TEST${connection.variantChars.local}FIL`; - const member = `TEST${connection.variantChars.local}MBR`; - const ccsid = connection.getCcsid(); - - if (library.includes(`$`)) { - await connection.runCommand({ command: `DLTLIB LIB(${library})`, noLibList: true }); - - const crtLib = await connection.runCommand({ command: `CRTLIB LIB(${library}) TYPE(*PROD)`, noLibList: true }); - if (Tools.parseMessages(crtLib.stderr).findId("CPD0032")) { - return; - } - - const createSourceFileCommand = await connection.runCommand({ command: `CRTSRCPF FILE(${library}/${sourceFile}) RCDLEN(112) CCSID(${ccsid})`, noLibList: true }); - assert.strictEqual(createSourceFileCommand.code, 0); - - const addPf = await connection.runCommand({ command: `ADDPFM FILE(${library}/${sourceFile}) MBR(${member}) SRCTYPE(TXT)`, noLibList: true }); - assert.strictEqual(addPf.code, 0); - - await connection.content.uploadMemberContent(undefined, library, sourceFile, member, [`**free`, `dsply 'Hello world';`, `return;`].join(`\n`)); - - // Ensure program compiles with dollar sign in current library - const compileResultA = await connection.runCommand({ command: `CRTBNDRPG PGM(${library}/${member}) SRCFILE(${library}/${sourceFile}) SRCMBR(${member})`, env: {'&CURLIB': library} }); - assert.strictEqual(compileResultA.code, 0); - - // Ensure program compiles with dollar sign in current library - const compileResultB = await connection.runCommand({ command: `CRTBNDRPG PGM(${library}/${member}) SRCFILE(${library}/${sourceFile}) SRCMBR(${member})`, env: {'&LIBL': library} }); - assert.strictEqual(compileResultB.code, 0); - } - } - }, { name: `Variant character in source names and commands`, test: async () => { // CHGUSRPRF X CCSID(284) CNTRYID(ES) LANGID(ESP) From 4bb26106f08aa38ae3eeac499bd13d0be2e548d2 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 17 Jan 2025 09:54:57 -0500 Subject: [PATCH 16/28] Remove unused test configuration and storage JSON files Signed-off-by: worksofliam --- src/api/tests/config.json | 51 -------------------------------------- src/api/tests/storage.json | 38 ---------------------------- 2 files changed, 89 deletions(-) delete mode 100644 src/api/tests/config.json delete mode 100644 src/api/tests/storage.json diff --git a/src/api/tests/config.json b/src/api/tests/config.json deleted file mode 100644 index d9b33c0f0..000000000 --- a/src/api/tests/config.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "connectionSettings": [ - { - "name": "testsystem", - "host": "", - "objectFilters": [], - "libraryList": [ - "QGPL", - "QTEMP", - "QDEVELOP", - "QBLDSYS", - "QBLDSYSR" - ], - "autoClearTempData": false, - "customVariables": [], - "connectionProfiles": [], - "commandProfiles": [], - "ifsShortcuts": [ - "/home/LIAMA" - ], - "autoSortIFSShortcuts": false, - "homeDirectory": "/home/LIAMA", - "tempLibrary": "ILEDITOR", - "tempDir": "/tmp", - "currentLibrary": "QGPL", - "sourceASP": "", - "sourceFileCCSID": "*FILE", - "autoConvertIFSccsid": false, - "hideCompileErrors": [], - "enableSourceDates": false, - "sourceDateMode": "diff", - "sourceDateGutter": false, - "encodingFor5250": "default", - "terminalFor5250": "default", - "setDeviceNameFor5250": false, - "connectringStringFor5250": "localhost", - "autoSaveBeforeAction": false, - "showDescInLibList": false, - "debugPort": "8005", - "debugSepPort": "8008", - "debugUpdateProductionFiles": false, - "debugEnableDebugTracing": false, - "readOnlyMode": false, - "quickConnect": true, - "defaultDeploymentMethod": "", - "protectedPaths": [], - "showHiddenFiles": true, - "lastDownloadLocation": "/Users/barry" - } - ] -} \ No newline at end of file diff --git a/src/api/tests/storage.json b/src/api/tests/storage.json deleted file mode 100644 index f25e0bfd8..000000000 --- a/src/api/tests/storage.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "serverSettingsCache_testsystem": { - "aspInfo": {}, - "qccsid": 65535, - "jobCcsid": 37, - "remoteFeatures": { - "git": "/QOpenSys/pkgs/bin/git", - "grep": "/QOpenSys/pkgs/bin/grep", - "tn5250": "/QOpenSys/pkgs/bin/tn5250", - "setccsid": "/usr/bin/setccsid", - "md5sum": "/QOpenSys/pkgs/bin/md5sum", - "bash": "/QOpenSys/pkgs/bin/bash", - "chsh": "/QOpenSys/pkgs/bin/chsh", - "stat": "/QOpenSys/pkgs/bin/stat", - "sort": "/QOpenSys/pkgs/bin/sort", - "GETNEWLIBL.PGM": "/QSYS.lib/ILEDITOR.lib/GETNEWLIBL.PGM", - "QZDFMDB2.PGM": "/QSYS.LIB/QZDFMDB2.PGM", - "startDebugService.sh": "/QIBM/ProdData/IBMiDebugService/bin/startDebugService.sh", - "attr": "/usr/bin/attr", - "iconv": "/usr/bin/iconv", - "tar": "/QOpenSys/pkgs/bin/tar", - "ls": "/QOpenSys/pkgs/bin/ls", - "find": "/QOpenSys/pkgs/bin/find", - "jdk80": "/QOpenSys/QIBM/ProdData/JavaVM/jdk80/64bit", - "jdk11": "/QOpenSys/QIBM/ProdData/JavaVM/jdk11/64bit", - "jdk17": "/QOpenSys/QIBM/ProdData/JavaVM/jdk17/64bit", - "openjdk11": "/QOpensys/pkgs/lib/jvm/openjdk-11", - "uname": "/usr/bin/uname" - }, - "remoteFeaturesKeys": "GETMBRINFO.SQL,GETNEWLIBL.PGM,QZDFMDB2.PGM,attr,bash,chsh,find,git,grep,iconv,jdk11,jdk17,jdk80,ls,md5sum,openjdk11,setccsid,sort,startDebugService.sh,stat,tar,tn5250,uname", - "badDataAreasChecked": true, - "libraryListValidated": true, - "pathChecked": true, - "userDefaultCCSID": 37, - "debugConfigLoaded": false, - "maximumArgsLength": 4191784 - } -} \ No newline at end of file From c3d6d5afb96f842ff2d3ad5be9d62580d0bbf1e6 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 17 Jan 2025 09:58:54 -0500 Subject: [PATCH 17/28] Swap over component tests to vitest Signed-off-by: worksofliam --- src/api/tests/suites/components.test.ts | 49 +++++++++++++++++++ src/testing/components.ts | 64 ------------------------- src/testing/index.ts | 4 +- 3 files changed, 50 insertions(+), 67 deletions(-) create mode 100644 src/api/tests/suites/components.test.ts delete mode 100644 src/testing/components.ts diff --git a/src/api/tests/suites/components.test.ts b/src/api/tests/suites/components.test.ts new file mode 100644 index 000000000..6c593263f --- /dev/null +++ b/src/api/tests/suites/components.test.ts @@ -0,0 +1,49 @@ +import { describe, it, expect } from 'vitest'; +import { GetMemberInfo } from '../../components/getMemberInfo'; +import { GetNewLibl } from '../../components/getNewLibl'; +import { Tools } from '../../Tools'; +import { getConnection } from '../state'; + + describe('Component Tests', () => { + it('Get new libl', async () => { + const connection = getConnection(); + const component = connection.getComponent(GetNewLibl.ID); + + if (component) { + const newLibl = await component.getLibraryListFromCommand(connection, `CHGLIBL CURLIB(SYSTOOLS)`); + expect(newLibl?.currentLibrary).toBe(`SYSTOOLS`); + } else { + throw new Error(`Component not installed`); + } + }); + + it('Check getMemberInfo', async () => { + const connection = getConnection(); + const component = connection?.getComponent(GetMemberInfo.ID)!; + + expect(component).toBeTruthy(); + + const memberInfoA = await component.getMemberInfo(connection, `QSYSINC`, `H`, `MATH`); + expect(memberInfoA).toBeTruthy(); + expect(memberInfoA?.library).toBe(`QSYSINC`); + expect(memberInfoA?.file).toBe(`H`); + expect(memberInfoA?.name).toBe(`MATH`); + expect(memberInfoA?.extension).toBe(`C`); + expect(memberInfoA?.text).toBe(`STANDARD HEADER FILE MATH`); + + const memberInfoB = await component.getMemberInfo(connection, `QSYSINC`, `H`, `MEMORY`); + expect(memberInfoB).toBeTruthy(); + expect(memberInfoB?.library).toBe(`QSYSINC`); + expect(memberInfoB?.file).toBe(`H`); + expect(memberInfoB?.name).toBe(`MEMORY`); + expect(memberInfoB?.extension).toBe(`CPP`); + expect(memberInfoB?.text).toBe(`C++ HEADER`); + + try { + await component.getMemberInfo(connection, `QSYSINC`, `H`, `OH_NONO`); + } catch (error: any) { + expect(error).toBeInstanceOf(Tools.SqlError); + expect(error.sqlstate).toBe("38501"); + } + }); + }); diff --git a/src/testing/components.ts b/src/testing/components.ts deleted file mode 100644 index 85ec96879..000000000 --- a/src/testing/components.ts +++ /dev/null @@ -1,64 +0,0 @@ -import assert from "assert"; -import { TestSuite } from "."; -import { Tools } from "../api/Tools"; -import { GetMemberInfo } from "../api/components/getMemberInfo"; -import { GetNewLibl } from "../api/components/getNewLibl"; -import { instance } from "../instantiate"; - -export const ComponentSuite: TestSuite = { - name: `Component tests`, - before: async () => { - const config = instance.getConfig()!; - assert.ok(config.enableSourceDates, `Source dates must be enabled for this test.`); - }, - - tests: [ - { - name: `Get new libl`, test: async () => { - const connection = instance.getConnection()! - const component = connection.getComponent(GetNewLibl.ID); - - if (component) { - const newLibl = await component.getLibraryListFromCommand(connection, `CHGLIBL CURLIB(SYSTOOLS)`); - - assert.strictEqual(newLibl?.currentLibrary, `SYSTOOLS`); - - } else { - assert.fail(`Component not installed`); - } - }, - }, - { - name: `Check getMemberInfo`, test: async () => { - const connection = instance.getConnection()!; - const component = connection?.getComponent(GetMemberInfo.ID)!; - - assert.ok(component); - - const memberInfoA = await component.getMemberInfo(connection, `QSYSINC`, `H`, `MATH`); - assert.ok(memberInfoA); - assert.strictEqual(memberInfoA?.library === `QSYSINC`, true); - assert.strictEqual(memberInfoA?.file === `H`, true); - assert.strictEqual(memberInfoA?.name === `MATH`, true); - assert.strictEqual(memberInfoA?.extension === `C`, true); - assert.strictEqual(memberInfoA?.text === `STANDARD HEADER FILE MATH`, true); - - const memberInfoB = await component.getMemberInfo(connection, `QSYSINC`, `H`, `MEMORY`); - assert.ok(memberInfoB); - assert.strictEqual(memberInfoB?.library === `QSYSINC`, true); - assert.strictEqual(memberInfoB?.file === `H`, true); - assert.strictEqual(memberInfoB?.name === `MEMORY`, true); - assert.strictEqual(memberInfoB?.extension === `CPP`, true); - assert.strictEqual(memberInfoB?.text === `C++ HEADER`, true); - - try{ - await component.getMemberInfo(connection, `QSYSINC`, `H`, `OH_NONO`) - } - catch(error: any){ - assert.ok(error instanceof Tools.SqlError); - assert.strictEqual(error.sqlstate, "38501"); - } - } - }, - ] -}; diff --git a/src/testing/index.ts b/src/testing/index.ts index 693f523de..9fefeee9a 100644 --- a/src/testing/index.ts +++ b/src/testing/index.ts @@ -2,7 +2,6 @@ import { env } from "process"; import vscode from "vscode"; import { instance } from "../instantiate"; import { ActionSuite } from "./action"; -import { ComponentSuite } from "./components"; import { ContentSuite } from "./content"; import { DeployToolsSuite } from "./deployTools"; import { EncodingSuite } from "./encoding"; @@ -23,8 +22,7 @@ const suites: TestSuite[] = [ FilterSuite, SearchSuite, StorageSuite, - EncodingSuite, - ComponentSuite + EncodingSuite ] export type TestSuite = { From 8187beec764254409224329967a06af39a62713a Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 17 Jan 2025 10:01:55 -0500 Subject: [PATCH 18/28] Move over filter tests Signed-off-by: worksofliam --- .../tests/suites/filter.test.ts} | 168 ++++++++---------- src/testing/index.ts | 2 - 2 files changed, 77 insertions(+), 93 deletions(-) rename src/{testing/filter.ts => api/tests/suites/filter.test.ts} (57%) diff --git a/src/testing/filter.ts b/src/api/tests/suites/filter.test.ts similarity index 57% rename from src/testing/filter.ts rename to src/api/tests/suites/filter.test.ts index db7419d00..35157e244 100644 --- a/src/testing/filter.ts +++ b/src/api/tests/suites/filter.test.ts @@ -1,98 +1,84 @@ -import assert from "assert"; -import { TestSuite } from "."; -import { parseFilter, singleGenericName } from "../api/Filter"; +import { describe, it, expect } from 'vitest'; +import { parseFilter, singleGenericName } from '../../Filter'; const QSYSINCS = ["CMRPG", "DSQCOMMR", "ECACHCMD", "ECARTCMD", "ECHGPRF1", "ECHGRCV1", "ECHKPWD1", "ECLRMST", "ECRTPRF1", "EDBOPNDB", "EDCVARY", "EDLTKR", "EDLTPRF1", "EDLTPRF2", "EDLTRCV1", "EIM", "EIMGEPH", "EJOBNTFY", "EKICONR", "EMHDFTPG", "EMOOPTEP", "ENPSEP", "EOGDOCH", "EOK", "EOKDRSH1", "EOKDRSP", "EOKDRVF", "EPADSEL", "EPDTRCJB", "EPQMAPXT", "EPQXFORM", "EPWFSEP", "EQQQRYGV", "EQQQRYSV", "ERRNO", "ERSTPRF1", "ERWSCI", "ESCWCHT", "ESETMST", "ESOEXTPT", "ESPBLSEP", "ESPDQRCD", "ESPDRVXT", "ESPTRNXT", "ESPXPTS", "ESYDRAPP", "ESYRGAPP", "ESYUPDCA", "ESYUPDCU", "ETASTGEX", "ETATAPMG", "ETEPGMST", "ETEPSEPH", "ETGDEVEX", "ETNCMTRB", "ETOCSVRE", "ETRNKSF", "EUIAFEX", "EUIALCL", "EUIALEX", "EUICSEX", "EUICTEX", "EUIFKCL", "EUIGPEX", "EUIILEX", "EUIMICL", "EUITAEX", "EVLDPWD1", "EWCPRSEP", "EWCPWRD", "EZDAEP", "EZHQEP", "EZRCEP", "EZSCEP", "EZSOEP", "FCNTL", "ICONV", "IFS", "JNI", "PTHREAD", "QALRTVA", "QANE", "QBNCHGPD", "QBNLMODI", "QBNLPGMI", "QBNLSPGM", "QBNRMODI", "QBNRPII", "QBNRSPGM", "QC3CCI", "QCAPCMD", "QCAVFY", "QCDRCMDD", "QCDRCMDI", "QCLRPGMI", "QCST", "QCSTCFG", "QCSTCFG1", "QCSTCHT", "QCSTCRG1", "QCSTCRG3", "QCSTCRG4", "QCSTCTL", "QCSTCTL1", "QCSTCTL2", "QCSTDD", "QDBJRNL", "QDBLDBR", "QDBRJBRL", "QDBRPLAY", "QDBRRCDL", "QDBRTVFD", "QDBRTVSN", "QDBST", "QDCCCFGD", "QDCLCFGD", "QDCRCFGS", "QDCRCTLD", "QDCRDEVD", "QDCRLIND", "QDCRNWSD", "QDFRPRTA", "QDFRTVFD", "QDMLOPNF", "QDMRTVFO", "QEDCHGIN", "QEDRTVCI", "QESCPTFO", "QESRSRVA", "QEZCHBKL", "QEZCHBKS", "QEZLSGNU", "QEZOLBKL", "QEZRTBKD", "QEZRTBKH", "QEZRTBKO", "QEZRTBKS", "QFPADAP1", "QFPADOLD", "QFPADOLS", "QFPADOLU", "QFPADRNI", "QFPADRUA", "QFPRLNWS", "QFPRRNWS", "QFPZAAPI", "QFVLSTA", "QFVLSTNL", "QFVRTVCD", "QGLDPAPI", "QGLDUAPI", "QGY", "QGYFNDF", "QGYGTLE", "QGYOLAFP", "QGYOLJBL", "QGYOLJOB", "QGYOLMSG", "QGYOLOBJ", "QGYOLSPL", "QGYRATLO", "QGYRHRCM", "QGYRPRTA", "QGYRPRTL", "QGYRTVSJ", "QHF", "QHFLSTFS", "QHFRDDR", "QIMGAPII", "QITDRSTS", "QJOJRNENT", "QJORJIDI", "QJOSJRNE", "QJOURNAL", "QKRBSPNEGO", "QLEAWI", "QLG", "QLGLCL", "QLGRLNGI", "QLGRTVCD", "QLGRTVCI", "QLGRTVCT", "QLGRTVLI", "QLGRTVSS", "QLGSORT", "QLGSRTIO", "QLIJRNL", "QLIRLIBD", "QLP", "QLPINSLP", "QLPLPRDS", "QLPRAGR", "QLYWRTBI", "QLZA", "QLZAADDK", "QLZADDLI", "QLZAGENK", "QLZARTV", "QLZARTVK", "QMHCTLJL", "QMHLJOBL", "QMHLSTM", "QMHOLHST", "QMHQCDQ", "QMHQJRNL", "QMHQRDQD", "QMHRCVM", "QMHRCVPM", "QMHRDQM", "QMHRMFAT", "QMHRMQAT", "QMHRSNEM", "QMHRTVM", "QMHRTVRQ", "QMR", "QMRAP1", "QNMRCVDT", "QNMRGFN", "QNMRGTI", "QNMRRGF", "QOGRTVOE", "QOKDSPDP", "QOKSCHD", "QOLQLIND", "QOLRECV", "QOLSEND", "QOLSETF", "QP0LFLOP", "QP0LROR", "QP0LRRO", "QP0LSCAN", "QP0LSTDI", "QP0MSRTVSO", "QPASTRPT", "QPDETCPP", "QPDETCVT", "QPDETPOL", "QPDETRPD", "QPDETRTV", "QPDETSND", "QPDETWCH", "QPDSRVPG", "QPMAAPI", "QPMDCPRM", "QPMLPFRD", "QPMLPMGT", "QPQ", "QPQAPME", "QPQMAP", "QPQOLPM", "QPQRAFPI", "QPQRPME", "QPTRTVPO", "QPZCPYSV", "QPZCRTFX", "QPZGENNM", "QPZGROUP", "QPZLOGFX", "QPZLSTFX", "QPZRTVFX", "QQQQRY", "QRCVDTAQ", "QRZRRSI", "QRZSCHE", "QSCCHGCT", "QSCJOINT", "QSCRWCHI", "QSCRWCHL", "QSCRXMLI", "QSCSWCH", "QSNAPI", "QSOTLSA", "QSPBOPNC", "QSPBSEPP", "QSPEXTWI", "QSPGETSP", "QSPMOVJB", "QSPMOVSP", "QSPOLJBQ", "QSPOLOTQ", "QSPRILSP", "QSPRJOBQ", "QSPROUTQ", "QSPRWTRI", "QSPSETWI", "QSPSNDWM", "QSPSPLI", "QSQCHKS", "QSQGNDDL", "QSQPRCED", "QSR", "QSRLIB01", "QSRLSAVF", "QSRRSTO", "QSRSAVO", "QSXFTRPB", "QSXSRVPL", "QSY", "QSYDIGID", "QSYEIMAPI", "QSYJRNL", "QSYLATLO", "QSYLAUTU", "QSYLOBJA", "QSYLOBJP", "QSYLUSRA", "QSYOLUC", "QSYOLVLE", "QSYRAUTU", "QSYREG", "QSYRTVAI", "QSYRTVSA", "QSYRTVSE", "QSYRTVUA", "QSYRUPWD", "QSYRUSRA", "QSYRUSRI", "QSYSUPWD", "QSYUSRIN", "QSYVLDL", "QSZCRTPD", "QSZCRTPL", "QSZPKGPO", "QSZRTVPR", "QSZSLTPR", "QSZSPTPR", "QTACJMA", "QTACTLDV", "QTAFROBJ", "QTARCGYL", "QTARCTGF", "QTARCTGI", "QTARDCAP", "QTARDINF", "QTARDSTS", "QTARJMA", "QTARTLBL", "QTASCTGF", "QTECRTVS", "QTEDBGS", "QTEDBGSI", "QTEDMPV", "QTERTVPV", "QTES", "QTHMCTLT", "QTMMSNDM", "QTMSCRTSNM", "QTNADDCR", "QTNCHGCO", "QTNRCMTI", "QTNXADTP", "QTOBUPDT", "QTOCC4IF", "QTOCCVTI", "QTOCLPPJ", "QTOCNETSTS", "QTOCPPPAPI", "QTOOSPF1", "QTOQMONAPI", "QTQICONV", "QTRXRLRL", "QTRXRLSA", "QTRXRLSL", "QTVOPNVT", "QTWAIDSP", "QTWCHKSP", "QUHRHLPT", "QUS", "QUSADDUI", "QUSCUSAT", "QUSEC", "QUSGEN", "QUSLFLD", "QUSLJOB", "QUSLMBR", "QUSLOBJ", "QUSLRCD", "QUSLSPL", "QUSREG", "QUSRJOBI", "QUSRMBRD", "QUSROBJD", "QUSRSPLA", "QUSRUIAT", "QUSRUSAT", "QVOIRCLD", "QVOIRCLG", "QVTRMSTG", "QWCADJTM", "QWCATTR", "QWCCHGJP", "QWCCHGPL", "QWCCHGTN", "QWCCVTDT", "QWCJBITP", "QWCJRNL", "QWCLASBS", "QWCLOBJL", "QWCLSCDE", "QWCOLTHD", "QWCRCLSI", "QWCRDTAA", "QWCRIPLA", "QWCRJBLK", "QWCRLCKI", "QWCRLRQI", "QWCRNETA", "QWCRSSTS", "QWCRSVAL", "QWCRTVCA", "QWCRTVTM", "QWCRTVTZ", "QWDCSBSE", "QWDLSBSE", "QWDLSJBQ", "QWDRJOBD", "QWDRSBSD", "QWPZ", "QWPZTAFP", "QWSRTVOI", "QWTCHGJB", "QWTRMVJL", "QWTRTVPX", "QWTRTVTA", "QWTSETPX", "QWVOLACT", "QWVOLAGP", "QWVRCSTK", "QXDADBBK", "QXDAEDRS", "QYASPOL", "QYASRDI", "QYASRDMS", "QYASRTVDDD", "QYASSDMO", "QYCDCUSG", "QYCDRCUI", "QYCUCERTI", "QYDOCOMMON", "QYDORTVR", "QYPERPEX", "QYPSCOLL", "QYPSSRVS", "QZCACLT", "QZD", "QZDMMDTA", "QZIPUTIL", "QZLS", "QZLSCHSI", "QZLSLSTI", "QZLSOLST", "QZMF", "QZMFASRV", "QZNFNFSO", "QZNFRTVE", "SCHED", "SIGNAL", "SQL", "SQLCLI", "SQLENV", "SQLFP", "SQLSCDS", "SQLUDF", "SYSIPC", "SYSSEM", "SYSSTAT", "SYSTYPES", "TIME", "TRGBUF", "UNISTD"]; -export const FilterSuite: TestSuite = { - name: `Filter API tests`, - tests: [ - { - name: `Simple 'ends with'`, test: async () => { - const filter = parseFilter("*cmd", 'simple'); - const filtered = QSYSINCS.filter(t => filter.test(t)); - assert.strictEqual(filtered.length, 3); - assert.strictEqual(filtered.filter(t => t.endsWith("CMD")).length, filtered.length); - } - }, - { - name: `Simple 'starts with'`, test: async () => { - const filter = parseFilter("sql*", 'simple'); - const filtered = QSYSINCS.filter(t => filter.test(t)); - assert.strictEqual(filtered.length, 6); - assert.strictEqual(filtered.filter(t => t.startsWith("SQL")).length, filtered.length); - } - }, - { - name: `Simple 'contains'`, test: async () => { - const filter = parseFilter("*USR*", 'simple'); - const filtered = QSYSINCS.filter(t => filter.test(t)); - assert.strictEqual(filtered.length, 11); - assert.strictEqual(filtered.filter(t => t.includes("USR")).length, filtered.length); - } - }, - { - name: `Multiple simples`, test: async () => { - const filter = parseFilter("SQL*,*CMD,*USR*", 'simple'); - const filtered = QSYSINCS.filter(t => filter.test(t)); - assert.strictEqual(filtered.length, 20); - assert.strictEqual(filtered.filter(t => t.startsWith("SQL") || t.endsWith("CMD") || t.includes("USR")).length, filtered.length); - } - }, - { - name: `Multiple simples with whitespaces`, test: async () => { - const filter = parseFilter(" SQL*,*CMD , *USR* ", 'simple'); - const filtered = QSYSINCS.filter(t => filter.test(t)); - assert.strictEqual(filtered.length, 20); - assert.strictEqual(filtered.filter(t => t.startsWith("SQL") || t.endsWith("CMD") || t.includes("USR")).length, filtered.length); - } - }, - { - name: `RegExp`, test: async () => { - const filter = parseFilter("^[^E].*CHG.*$", 'regex'); - const filtered = QSYSINCS.filter(t => filter.test(t)); - assert.strictEqual(filtered.length, 8); - assert.strictEqual(filtered.filter(t => !t.startsWith("E") && t.indexOf("CHG")).length, filtered.length); - } - }, - { - name: `Is case insensitive`, test: async () => { - const lowerCaseFilter = parseFilter("sql*", 'simple'); - const upperCaseFilter = parseFilter("SQL*", 'simple'); - const mixedCaseFilter = parseFilter("SqL*", 'simple'); - const lowerCaseFiltered = QSYSINCS.filter(t => lowerCaseFilter.test(t)) - const upperCaseFiltered = QSYSINCS.filter(t => upperCaseFilter.test(t)) - const mixedCaseFiltered = QSYSINCS.filter(t => mixedCaseFilter.test(t)) +describe('Filter Tests', { concurrent: true }, () => { + it(`Simple 'ends with'`, () => { + const filter = parseFilter("*cmd", 'simple'); + const filtered = QSYSINCS.filter(t => filter.test(t)); + expect(filtered.length).toBe(3); + expect(filtered.filter(t => t.endsWith("CMD")).length).toBe(filtered.length); + }); - assert.strictEqual(lowerCaseFiltered.length, 6); - assert.strictEqual(upperCaseFiltered.length, 6); - assert.strictEqual(mixedCaseFiltered.length, 6); + it(`Simple 'starts with'`, () => { + const filter = parseFilter("sql*", 'simple'); + const filtered = QSYSINCS.filter(t => filter.test(t)); + expect(filtered.length).toBe(6); + expect(filtered.filter(t => t.startsWith("SQL")).length).toBe(filtered.length); + }); - assert.strictEqual(upperCaseFiltered.every(t => lowerCaseFiltered.includes(t)), true); - assert.strictEqual(lowerCaseFiltered.every(t => mixedCaseFiltered.includes(t)), true); - } - }, - { - name: `Is relevant`, test: async () => { - const notAFilter = parseFilter("QSYSINC", 'simple'); - const notAFilterEither = parseFilter("QSYSINC", 'regex'); - const aFilter = parseFilter("*QSYS*", 'simple'); + it(`Simple 'contains'`, () => { + const filter = parseFilter("*USR*", 'simple'); + const filtered = QSYSINCS.filter(t => filter.test(t)); + expect(filtered.length).toBe(11); + expect(filtered.filter(t => t.includes("USR")).length).toBe(filtered.length); + }); - assert.strictEqual(notAFilter.noFilter, true); - assert.strictEqual(notAFilterEither.noFilter, true); - assert.strictEqual(aFilter.noFilter, false); - }, - }, - { - name: `Single generic name`, test: async () => { - const generic = singleGenericName("SQL*"); - const notGeneric = singleGenericName("*SQL"); - const notGenericEither = singleGenericName("SQL*,QSYS*"); + it(`Multiple simples`, () => { + const filter = parseFilter("SQL*,*CMD,*USR*", 'simple'); + const filtered = QSYSINCS.filter(t => filter.test(t)); + expect(filtered.length).toBe(20); + expect(filtered.filter(t => t.startsWith("SQL") || t.endsWith("CMD") || t.includes("USR")).length).toBe(filtered.length); + }); - assert.strictEqual(generic, "SQL*"); - assert.strictEqual(notGeneric, undefined); - assert.strictEqual(notGenericEither, undefined); - } - } - ] -}; + it(`Multiple simples with whitespaces`, () => { + const filter = parseFilter(" SQL*,*CMD , *USR* ", 'simple'); + const filtered = QSYSINCS.filter(t => filter.test(t)); + expect(filtered.length).toBe(20); + expect(filtered.filter(t => t.startsWith("SQL") || t.endsWith("CMD") || t.includes("USR")).length).toBe(filtered.length); + }); + + it(`RegExp`, () => { + const filter = parseFilter("^[^E].*CHG.*$", 'regex'); + const filtered = QSYSINCS.filter(t => filter.test(t)); + expect(filtered.length).toBe(8); + expect(filtered.filter(t => !t.startsWith("E") && t.indexOf("CHG")).length).toBe(filtered.length); + }); + + it(`Is case insensitive`, () => { + const lowerCaseFilter = parseFilter("sql*", 'simple'); + const upperCaseFilter = parseFilter("SQL*", 'simple'); + const mixedCaseFilter = parseFilter("SqL*", 'simple'); + const lowerCaseFiltered = QSYSINCS.filter(t => lowerCaseFilter.test(t)); + const upperCaseFiltered = QSYSINCS.filter(t => upperCaseFilter.test(t)); + const mixedCaseFiltered = QSYSINCS.filter(t => mixedCaseFilter.test(t)); + + expect(lowerCaseFiltered.length).toBe(6); + expect(upperCaseFiltered.length).toBe(6); + expect(mixedCaseFiltered.length).toBe(6); + + expect(upperCaseFiltered.every(t => lowerCaseFiltered.includes(t))).toBe(true); + expect(lowerCaseFiltered.every(t => mixedCaseFiltered.includes(t))).toBe(true); + }); + + it(`Is relevant`, () => { + const notAFilter = parseFilter("QSYSINC", 'simple'); + const notAFilterEither = parseFilter("QSYSINC", 'regex'); + const aFilter = parseFilter("*QSYS*", 'simple'); + + expect(notAFilter.noFilter).toBe(true); + expect(notAFilterEither.noFilter).toBe(true); + expect(aFilter.noFilter).toBe(false); + }); + + it(`Single generic name`, () => { + const generic = singleGenericName("SQL*"); + const notGeneric = singleGenericName("*SQL"); + const notGenericEither = singleGenericName("SQL*,QSYS*"); + + expect(generic).toBe("SQL*"); + expect(notGeneric).toBeUndefined(); + expect(notGenericEither).toBeUndefined(); + }); +}); diff --git a/src/testing/index.ts b/src/testing/index.ts index 9fefeee9a..31409f459 100644 --- a/src/testing/index.ts +++ b/src/testing/index.ts @@ -5,7 +5,6 @@ import { ActionSuite } from "./action"; import { ContentSuite } from "./content"; import { DeployToolsSuite } from "./deployTools"; import { EncodingSuite } from "./encoding"; -import { FilterSuite } from "./filter"; import { ILEErrorSuite } from "./ileErrors"; import { SearchSuite } from "./search"; import { StorageSuite } from "./storage"; @@ -19,7 +18,6 @@ const suites: TestSuite[] = [ DeployToolsSuite, ToolsSuite, ILEErrorSuite, - FilterSuite, SearchSuite, StorageSuite, EncodingSuite From 4dc7e3a2470321ccfae5ece72bc0cfbe2a4b8def Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 17 Jan 2025 10:10:11 -0500 Subject: [PATCH 19/28] Move errors to vitest Signed-off-by: worksofliam --- src/api/tests/suites/errors.test.ts | 399 +++++++++++++++++++++++++++ src/testing/ileErrors.ts | 410 ---------------------------- src/testing/index.ts | 2 - 3 files changed, 399 insertions(+), 412 deletions(-) create mode 100644 src/api/tests/suites/errors.test.ts delete mode 100644 src/testing/ileErrors.ts diff --git a/src/api/tests/suites/errors.test.ts b/src/api/tests/suites/errors.test.ts new file mode 100644 index 000000000..e0e06f757 --- /dev/null +++ b/src/api/tests/suites/errors.test.ts @@ -0,0 +1,399 @@ +import { describe, it, expect } from 'vitest'; +import { parseErrors } from '../../errors/parser'; + +describe('Filter Tests', { concurrent: true }, () => { + it('Basic test (CRTSQLRPGI, member)', () => { + const lines = [ + `TIMESTAMP 0 20230524115628`, + `PROCESSOR 0 999 1`, + `FILEID 0 999 000000 026 QTEMP/QSQLTEMP1(EMPLOYEES) 20230524115628 0`, + `FILEID 0 001 000000 026 LIAMA/QRPGLESRC(EMPLOYEES) 20230516152429 0`, + `ERROR 0 001 1 000044 000044 000 000044 000 SQL1001 S 30 048 External file definition for EMPLOYEE not found.`, + `ERROR 0 001 1 000093 000093 020 000093 020 SQL1103 W 10 069 Position 20 Column definitions for table EMPLOYEE in *LIBL not found.`, + `ERROR 0 001 1 000103 000103 019 000103 019 SQL0312 S 30 212 Position 19 Variable EMPLOYEE not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, + `ERROR 0 001 1 000103 000103 028 000103 028 SQL0312 S 30 209 Position 28 Variable EMPNO not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, + `ERROR 0 001 1 000104 000104 016 000104 016 SQL0312 S 30 212 Position 16 Variable EMPLOYEE not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, + `ERROR 0 001 1 000104 000104 025 000104 025 SQL0312 S 30 212 Position 25 Variable FIRSTNME not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, + `ERROR 0 001 1 000105 000105 016 000105 016 SQL0312 S 30 212 Position 16 Variable EMPLOYEE not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, + `ERROR 0 001 1 000105 000105 025 000105 025 SQL0312 S 30 212 Position 25 Variable LASTNAME not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, + `ERROR 0 001 1 000106 000106 016 000106 016 SQL0312 S 30 212 Position 16 Variable EMPLOYEE not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, + `ERROR 0 001 1 000106 000106 025 000106 025 SQL0312 S 30 207 Position 25 Variable JOB not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, + `EXPANSION 0 001 000000 000000 999 000049 000113`, + `EXPANSION 0 001 000096 000096 999 000154 000171`, + `EXPANSION 0 001 000096 000096 999 000180 000193`, + `EXPANSION 0 001 000106 000106 999 000204 000207`, + `EXPANSION 0 001 000121 000121 999 000223 000234`, + `FILEEND 0 001 000151`, + `FILEEND 0 999 000264` + ]; + + const errors = parseErrors(lines); + + const filePath = `LIAMA/QRPGLESRC/EMPLOYEES`; + + expect(errors.size).toBe(1); + expect(errors.has(filePath)).toBe(true); + + const fileErrors = errors.get(filePath); + expect(fileErrors).toBeDefined(); + expect(fileErrors?.length).toBe(10); + + const errorA = fileErrors!.find(err => err.lineNum === 44); + expect(errorA).toBeDefined(); + + expect(errorA?.code).toBe(`SQL1001`); + expect(errorA?.lineNum).toBe(44); + expect(errorA?.toLineNum).toBe(44); + expect(errorA?.column).toBe(0); + expect(errorA?.toColumn).toBe(0); + expect(errorA?.sev).toBe(30); + expect(errorA?.text).toBe(`External file definition for EMPLOYEE not found.`); + + const lineErrors = fileErrors!.filter(err => err.lineNum === 104); + expect(lineErrors.length).toBe(2); + + expect(lineErrors[0]?.code).toBe(`SQL0312`); + expect(lineErrors[0]?.lineNum).toBe(104); + expect(lineErrors[0]?.toLineNum).toBe(104); + expect(lineErrors[0]?.column).toBe(16); + expect(lineErrors[0]?.toColumn).toBe(16); + expect(lineErrors[0]?.sev).toBe(30); + expect(lineErrors[0]?.text).toBe(`Position 16 Variable EMPLOYEE not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`); + + expect(lineErrors[1]?.code).toBe(`SQL0312`); + expect(lineErrors[1]?.lineNum).toBe(104); + expect(lineErrors[1]?.toLineNum).toBe(104); + expect(lineErrors[1]?.column).toBe(25); + expect(lineErrors[1]?.toColumn).toBe(25); + expect(lineErrors[1]?.sev).toBe(30); + expect(lineErrors[1]?.text).toBe(`Position 25 Variable FIRSTNME not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`); + }); + + it('Basic test (CRTSQLRPGI, streamfile)', () => { + const lines = [ + `TIMESTAMP 0 20230524122108`, + `PROCESSOR 0 999 1`, + `FILEID 0 999 000000 026 QTEMP/QSQLTEMP1(EMPLOYEES) 20230524122108 0`, + `FILEID 0 001 000000 071 /home/LINUX/builds/ibmi-company_system/qrpglesrc/employees.pgm.sqlrpgle 20230429182220 0`, + `ERROR 0 001 1 000041 000041 000 000041 000 SQL1001 S 30 048 External file definition for EMPLOYEE not found.`, + `ERROR 0 001 1 000095 000095 020 000095 020 SQL1103 W 10 069 Position 20 Column definitions for table EMPLOYEE in *LIBL not found.`, + `ERROR 0 001 1 000105 000105 025 000105 025 SQL0312 S 30 212 Position 25 Variable EMPLOYEE not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, + `ERROR 0 001 1 000105 000105 034 000105 034 SQL0312 S 30 209 Position 34 Variable EMPNO not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, + `ERROR 0 001 1 000106 000106 025 000106 025 SQL0312 S 30 212 Position 25 Variable EMPLOYEE not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, + `ERROR 0 001 1 000106 000106 034 000106 034 SQL0312 S 30 212 Position 34 Variable FIRSTNME not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, + `ERROR 0 001 1 000107 000107 025 000107 025 SQL0312 S 30 212 Position 25 Variable EMPLOYEE not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, + `ERROR 0 001 1 000107 000107 034 000107 034 SQL0312 S 30 212 Position 34 Variable LASTNAME not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, + `ERROR 0 001 1 000108 000108 025 000108 025 SQL0312 S 30 212 Position 25 Variable EMPLOYEE not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, + `ERROR 0 001 1 000108 000108 034 000108 034 SQL0312 S 30 207 Position 34 Variable JOB not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, + `EXPANSION 0 001 000000 000000 999 000051 000115`, + `EXPANSION 0 001 000098 000098 999 000154 000171`, + `EXPANSION 0 001 000098 000098 999 000182 000195`, + `EXPANSION 0 001 000108 000108 999 000206 000209`, + `EXPANSION 0 001 000123 000123 999 000225 000236`, + `FILEEND 0 001 000153`, + `FILEEND 0 999 000266` + ]; + + const errors = parseErrors(lines); + + const filePath = `/home/LINUX/builds/ibmi-company_system/qrpglesrc/employees.pgm.sqlrpgle`; + + expect(errors.size).toBe(1); + expect(errors.has(filePath)).toBe(true); + + const fileErrors = errors.get(filePath); + expect(fileErrors).toBeDefined(); + expect(fileErrors?.length).toBe(10); + + const errorA = fileErrors!.find(err => err.lineNum === 41); + expect(errorA).toBeDefined(); + + expect(errorA?.code).toBe(`SQL1001`); + expect(errorA?.lineNum).toBe(41); + expect(errorA?.toLineNum).toBe(41); + expect(errorA?.column).toBe(0); + expect(errorA?.toColumn).toBe(0); + expect(errorA?.sev).toBe(30); + expect(errorA?.text).toBe(`External file definition for EMPLOYEE not found.`); + + const lineErrors = fileErrors!.filter(err => err.lineNum === 106); + expect(lineErrors.length).toBe(2); + + expect(lineErrors[0]?.code).toBe(`SQL0312`); + expect(lineErrors[0]?.lineNum).toBe(106); + expect(lineErrors[0]?.toLineNum).toBe(106); + expect(lineErrors[0]?.column).toBe(25); + expect(lineErrors[0]?.toColumn).toBe(25); + expect(lineErrors[0]?.sev).toBe(30); + expect(lineErrors[0]?.text).toBe(`Position 25 Variable EMPLOYEE not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`); + + expect(lineErrors[1]?.code).toBe(`SQL0312`); + expect(lineErrors[1]?.lineNum).toBe(106); + expect(lineErrors[1]?.toLineNum).toBe(106); + expect(lineErrors[1]?.column).toBe(34); + expect(lineErrors[1]?.toColumn).toBe(34); + expect(lineErrors[1]?.sev).toBe(30); + expect(lineErrors[1]?.text).toBe(`Position 34 Variable FIRSTNME not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`); + }); + + it('Long source file containing spaces (CRTSQLRPGI, streamfile)', () => { + const lines = [ + `TIMESTAMP 0 20230405035632`, + `PROCESSOR 0 999 1`, + `FILEID 0 999 000000 024 QTEMP/QSQLTEMP1(FIX1200) 20230405035632 0`, + `FILEID 0 001 000000 646 /home/ANGELORPA/builds/sources/long-directory-name-for-testing-long-paths/subdirectory-with-a-long-name-for-testing-long-paths/another-subdirectory-with-a-long-name-for-testing-long-paths/one-more-subdirectory-this-is-the-last-one/01-long directory name w`, + `FILEIDCONT 0 001 000000 000 ith spaces in/02-long directory with space in his name/03-long directory name with space in for testing prupouse/04-long directory name with space in for testing event file parser/05-long directory name with space in for testing event file parser/06-long `, + `FILEIDCONT 0 001 000000 000 directory name with space in for testing event file parser/sorce file long name with space in for testing event file parser.pmg.sqlrpgle 20230403024018 0`, + `ERROR 0 999 2 000000 000000 000 000000 000 SQL0053 W 10 024 No SQL statements found.`, + `EXPANSION 0 001 000000 000000 999 000006 000070`, + `FILEEND 0 001 000009`, + `FILEEND 0 999 000074`, + `PROCESSOR 0 000 1`, + `FILEID 0 001 000000 046 /QSYS.LIB/QTEMP.LIB/QSQLTEMP1.FILE/FIX1200.MBR 20230405035632 0`, + `ERROR 0 001 1 000072 000072 001 000072 005 RNF5377 E 20 038 The end of the expression is expected.`, + `ERROR 0 001 1 000072 000072 001 000072 005 RNF7030 S 30 043 The name or indicator DSPLY is not defined.`, + `ERROR 0 001 1 000070 000070 014 000070 019 RNF7031 I 00 047 The name or indicator SQFAPP is not referenced.`, + `ERROR 0 001 1 000068 000068 014 000068 019 RNF7031 I 00 047 The name or indicator SQFCRT is not referenced.`, + `ERROR 0 001 1 000069 000069 014 000069 019 RNF7031 I 00 047 The name or indicator SQFOVR is not referenced.`, + `ERROR 0 001 1 000067 000067 014 000067 018 RNF7031 I 00 046 The name or indicator SQFRD is not referenced.`, + `ERROR 0 001 1 000010 000010 011 000010 016 RNF7031 I 00 047 The name or indicator SQLAID is not referenced.`, + `ERROR 0 001 1 000012 000012 011 000012 016 RNF7031 I 00 047 The name or indicator SQLABC is not referenced.`, + `ERROR 0 001 1 000014 000014 011 000014 016 RNF7031 I 00 047 The name or indicator SQLCOD is not referenced.`, + `ERROR 0 001 1 000016 000016 011 000016 016 RNF7031 I 00 047 The name or indicator SQLERL is not referenced.`, + `ERROR 0 001 1 000018 000018 011 000018 016 RNF7031 I 00 047 The name or indicator SQLERM is not referenced.`, + `ERROR 0 001 1 000020 000020 011 000020 016 RNF7031 I 00 047 The name or indicator SQLERP is not referenced.`, + `ERROR 0 001 1 000022 000022 011 000022 016 RNF7031 I 00 047 The name or indicator SQLER1 is not referenced.`, + `ERROR 0 001 1 000023 000023 011 000023 016 RNF7031 I 00 047 The name or indicator SQLER2 is not referenced.`, + `ERROR 0 001 1 000024 000024 011 000024 016 RNF7031 I 00 047 The name or indicator SQLER3 is not referenced.`, + `ERROR 0 001 1 000025 000025 011 000025 016 RNF7031 I 00 047 The name or indicator SQLER4 is not referenced.`, + `ERROR 0 001 1 000026 000026 011 000026 016 RNF7031 I 00 047 The name or indicator SQLER5 is not referenced.`, + `ERROR 0 001 1 000027 000027 011 000027 016 RNF7031 I 00 047 The name or indicator SQLER6 is not referenced.`, + `ERROR 0 001 1 000028 000028 011 000028 017 RNF7031 I 00 048 The name or indicator SQLERRD is not referenced.`, + `ERROR 0 001 1 000030 000030 011 000030 016 RNF7031 I 00 047 The name or indicator SQLWN0 is not referenced.`, + `ERROR 0 001 1 000031 000031 011 000031 016 RNF7031 I 00 047 The name or indicator SQLWN1 is not referenced.`, + `ERROR 0 001 1 000032 000032 011 000032 016 RNF7031 I 00 047 The name or indicator SQLWN2 is not referenced.`, + `ERROR 0 001 1 000033 000033 011 000033 016 RNF7031 I 00 047 The name or indicator SQLWN3 is not referenced.`, + `ERROR 0 001 1 000034 000034 011 000034 016 RNF7031 I 00 047 The name or indicator SQLWN4 is not referenced.`, + `ERROR 0 001 1 000035 000035 011 000035 016 RNF7031 I 00 047 The name or indicator SQLWN5 is not referenced.`, + `ERROR 0 001 1 000036 000036 011 000036 016 RNF7031 I 00 047 The name or indicator SQLWN6 is not referenced.`, + `ERROR 0 001 1 000037 000037 011 000037 016 RNF7031 I 00 047 The name or indicator SQLWN7 is not referenced.`, + `ERROR 0 001 1 000038 000038 011 000038 016 RNF7031 I 00 047 The name or indicator SQLWN8 is not referenced.`, + `ERROR 0 001 1 000039 000039 011 000039 016 RNF7031 I 00 047 The name or indicator SQLWN9 is not referenced.`, + `ERROR 0 001 1 000040 000040 011 000040 016 RNF7031 I 00 047 The name or indicator SQLWNA is not referenced.`, + `ERROR 0 001 1 000041 000041 011 000041 017 RNF7031 I 00 048 The name or indicator SQLWARN is not referenced.`, + `ERROR 0 001 1 000043 000043 011 000043 016 RNF7031 I 00 047 The name or indicator SQLSTT is not referenced.`, + `ERROR 0 001 1 000054 000054 015 000054 026 RNF7031 I 00 051 The name or indicator SQLCLSE... is not referenced.`, + `ERROR 0 001 1 000058 000058 015 000058 026 RNF7031 I 00 051 The name or indicator SQLCMIT... is not referenced.`, + `ERROR 0 001 1 000050 000050 015 000050 026 RNF7031 I 00 051 The name or indicator SQLOPEN... is not referenced.`, + `ERROR 0 001 1 000045 000045 015 000045 027 RNF7031 I 00 051 The name or indicator SQLROUT... is not referenced.`, + `ERROR 0 001 0 000000 000000 000 000000 000 RNS9308 T 50 057 Compilation stopped. Severity 30 errors found in program.`, + `FILEEND 0 001 000074` + ]; + + const errors = parseErrors(lines); + + // path containing whitespaces + const filePath = `/home/ANGELORPA/builds/sources/long-directory-name-for-testing-long-paths/subdirectory-with-a-long` + + `-name-for-testing-long-paths/another-subdirectory-with-a-long-name-for-testing-long-paths/one-more-subdirectory-this` + + `-is-the-last-one/01-long directory name with spaces in/02-long directory with space in his name/03-long directory name` + + ` with space in for testing prupouse/04-long directory name with space in for testing event file parser/05-long directory` + + ` name with space in for testing event file parser/06-long directory name with space in for testing event file parser/` + + `sorce file long name with space in for testing event file parser.pmg.sqlrpgle`; + + // erros.size is equal 1 as even the error in the intermediate file can be mapped to the original source file + expect(errors.size).toBe(1); + expect(errors.has(filePath)).toBe(true); + }); + + it('nested copybook (CRTBNDRPG, streamfile)', () => { + const lines = [ + `TIMESTAMP 0 20230619181512`, + `PROCESSOR 0 000 1`, + `FILEID 0 001 000000 060 /home/ANGELORPA/builds/fix1200/display/qrpglesrc/hello.rpgle 20230619181454 0`, + `FILEID 0 002 000004 063 /home/ANGELORPA/builds/fix1200/display/qprotsrc/constants.rpgle 20230619180115 0`, + `FILEID 0 003 000007 064 /home/ANGELORPA/builds/fix1200/display/qprotsrc/constLeve2.rpgle 20230619181501 0`, + `ERROR 0 003 1 000004 000004 002 000004 002 RNF0734 S 30 052 The statement must be complete before the file ends.`, + `FILEEND 0 003 000004`, + `ERROR 0 003 1 000004 000004 002 000004 002 RNF0734 S 30 052 The statement must be complete before the file ends.`, + `FILEEND 0 002 000007`, + `ERROR 0 001 1 000006 000006 001 000006 005 RNF3312 E 20 075 A keyword is specified more than once for a definition; keyword is ignored.`, + `ERROR 0 001 1 000006 000006 007 000006 011 RNF3312 E 20 075 A keyword is specified more than once for a definition; keyword is ignored.`, + `ERROR 0 001 1 000006 000006 013 000006 025 RNF3312 E 20 075 A keyword is specified more than once for a definition; keyword is ignored.`, + `ERROR 0 001 1 000012 000012 001 000012 001 RNF0637 S 30 068 An operand was expected but was not found; specification is ignored.`, + `ERROR 0 001 1 000012 000012 007 000012 007 RNF0637 S 30 068 An operand was expected but was not found; specification is ignored.`, + `ERROR 0 003 1 000003 000003 007 000003 010 RNF7031 I 00 045 The name or indicator FILE is not referenced.`, + `ERROR 0 002 1 000003 000003 007 000003 015 RNF7031 I 00 050 The name or indicator FIRST_DAY is not referenced.`, + `ERROR 0 001 1 000012 000012 002 000012 005 RNF7030 S 30 042 The name or indicator INLR is not defined.`, + `ERROR 0 002 1 000004 000004 007 000004 016 RNF7031 I 00 051 The name or indicator SECOND_DAY is not referenced.`, + `ERROR 0 001 0 000000 000000 000 000000 000 RNS9308 T 50 057 Compilation stopped.Severity 30 errors found in program.`, + `FILEEND 0 001 000013` + ] + + + const errors = parseErrors(lines); + + // 3 files (one main, one copybook and a nested copybook) + const filePath = `/home/ANGELORPA/builds/fix1200/display/qrpglesrc/hello.rpgle`; + const copybook_file_path = `/home/ANGELORPA/builds/fix1200/display/qprotsrc/constants.rpgle`; + const nested_copybook_file_path = `/home/ANGELORPA/builds/fix1200/display/qprotsrc/constLeve2.rpgle`; + + // erros.size is equal to the number of PROCESSOR records in the events file + expect(errors.size).toBe(3); + + // should be 3 different file paths + expect(errors.has(filePath)).toBe(true); + expect(errors.has(copybook_file_path)).toBe(true); + expect(errors.has(nested_copybook_file_path)).toBe(true); + + // main file errors + const fileErrors = errors.get(filePath); + expect(fileErrors).toBeDefined(); + expect(fileErrors?.length).toBe(7); + + // copybook file errors + const copybook_fileErrors = errors.get(copybook_file_path); + expect(copybook_fileErrors).toBeDefined(); + expect(copybook_fileErrors?.length).toBe(2); + + // nested copybook file errors + const nested_copybook_fileErrors = errors.get(nested_copybook_file_path); + expect(nested_copybook_fileErrors).toBeDefined(); + expect(nested_copybook_fileErrors?.length).toBe(3); + }); + + it('filter errors (CRTSQLRPGI, streamfile)', () => { + const lines = [ + "TIMESTAMP 0 20230815094609", + "PROCESSOR 0 999 1", + "FILEID 0 999 000000 024 QTEMP/QSQLPRE(EMPLOYEES) 20230815094609 0", + "FILEID 0 001 000000 077 /home/LINUX/builds/ibmi-company_system-rmake/qrpglesrc/employees.pgm.sqlrpgle 20230805131228 0", + "FILEID 0 002 000010 073 /home/LINUX/builds/ibmi-company_system-rmake/qrpgleref/constants.rpgleinc 20230804103719 0", + "EXPANSION 0 000 000000 000000 999 000011 000011", + "FILEEND 0 002 000026", + "EXPANSION 0 000 000000 000000 999 000038 000038", + "FILEEND 0 001 000145", + "FILEEND 0 999 000173", + "PROCESSOR 0 999 1", + "FILEID 0 999 000000 026 QTEMP/QSQLTEMP1(EMPLOYEES) 20230815094609 0", + "FILEID 0 001 000000 024 QTEMP/QSQLPRE(EMPLOYEES) 20230815094609 0", + "EXPANSION 0 001 000000 000000 999 000074 000138", + "EXPANSION 0 001 000118 000118 999 000176 000205", + "EXPANSION 0 001 000118 000118 999 000214 000227", + "EXPANSION 0 001 000128 000128 999 000238 000248", + "EXPANSION 0 001 000143 000143 999 000264 000275", + "FILEEND 0 001 000173", + "FILEEND 0 999 000305", + "PROCESSOR 0 000 1", + "FILEID 0 001 000000 048 /QSYS.LIB/QTEMP.LIB/QSQLTEMP1.FILE/EMPLOYEES.MBR 20230815094609 0", + "ERROR 0 001 1 000200 000200 009 000200 017 RNF7031 I 00 050 The name or indicator SQL_00016 is not referenced.", + "ERROR 0 001 1 000202 000202 009 000202 017 RNF7031 I 00 050 The name or indicator SQL_00018 is not referenced.", + "ERROR 0 001 1 000203 000203 009 000203 017 RNF7031 I 00 050 The name or indicator SQL_00019 is not referenced.", + "ERROR 0 001 1 000204 000204 009 000204 017 RNF7031 I 00 050 The name or indicator SQL_00020 is not referenced.", + "ERROR 0 001 1 000188 000188 009 000188 017 RNF7031 I 00 050 The name or indicator SQL_00007 is not referenced.", + "ERROR 0 001 1 000189 000189 009 000189 017 RNF7031 I 00 050 The name or indicator SQL_00008 is not referenced.", + "ERROR 0 001 1 000191 000191 009 000191 017 RNF7031 I 00 050 The name or indicator SQL_00010 is not referenced.", + "ERROR 0 001 1 000179 000179 009 000179 017 RNF7031 I 00 050 The name or indicator SQL_00001 is not referenced.", + "ERROR 0 001 1 000182 000182 009 000182 017 RNF7031 I 00 050 The name or indicator SQL_00004 is not referenced.", + "ERROR 0 001 1 000173 000173 009 000173 014 RNF7031 I 00 047 The name or indicator ACTION is not referenced.", + "ERROR 0 001 1 000070 000070 008 000070 015 RNF7031 I 00 048 The name or indicator MIDINIT is not referenced.", + "ERROR 0 001 1 000070 000070 008 000070 015 RNF7031 I 00 049 The name or indicator WORKDEPT is not referenced.", + "ERROR 0 001 1 000070 000070 008 000070 015 RNF7031 I 00 048 The name or indicator PHONENO is not referenced.", + "ERROR 0 001 1 000070 000070 008 000070 015 RNF7031 I 00 049 The name or indicator HIREDATE is not referenced.", + "ERROR 0 001 1 000070 000070 008 000070 015 RNF7031 I 00 048 The name or indicator EDLEVEL is not referenced.", + "ERROR 0 001 1 000070 000070 008 000070 015 RNF7031 I 00 044 The name or indicator SEX is not referenced.", + "ERROR 0 001 1 000070 000070 008 000070 015 RNF7031 I 00 050 The name or indicator BIRTHDATE is not referenced.", + "ERROR 0 001 1 000070 000070 008 000070 015 RNF7031 I 00 047 The name or indicator SALARY is not referenced.", + "ERROR 0 001 1 000070 000070 008 000070 015 RNF7031 I 00 046 The name or indicator BONUS is not referenced.", + "ERROR 0 001 1 000070 000070 008 000070 015 RNF7031 I 00 045 The name or indicator COMM is not referenced.", + "ERROR 0 001 1 000004 000004 008 000004 016 RNF7031 I 00 050 The name or indicator EMPLOYEES is not referenced.", + "ERROR 0 001 1 000042 000042 001 000042 000 RNF7089 I 00 051 RPG provides Separate-Indicator area for file EMPS.", + "ERROR 0 001 1 000012 000012 015 000012 017 RNF7031 I 00 044 The name or indicator F01 is not referenced.", + "ERROR 0 001 1 000013 000013 015 000013 017 RNF7031 I 00 044 The name or indicator F02 is not referenced.", + "ERROR 0 001 1 000014 000014 015 000014 017 RNF7031 I 00 044 The name or indicator F03 is not referenced.", + "ERROR 0 001 1 000015 000015 015 000015 017 RNF7031 I 00 044 The name or indicator F04 is not referenced.", + "ERROR 0 001 1 000016 000016 015 000016 017 RNF7031 I 00 044 The name or indicator F05 is not referenced.", + "ERROR 0 001 1 000017 000017 015 000017 017 RNF7031 I 00 044 The name or indicator F06 is not referenced.", + "ERROR 0 001 1 000018 000018 015 000018 017 RNF7031 I 00 044 The name or indicator F07 is not referenced.", + "ERROR 0 001 1 000019 000019 015 000019 017 RNF7031 I 00 044 The name or indicator F08 is not referenced.", + "ERROR 0 001 1 000020 000020 015 000020 017 RNF7031 I 00 044 The name or indicator F09 is not referenced.", + "ERROR 0 001 1 000021 000021 015 000021 017 RNF7031 I 00 044 The name or indicator F10 is not referenced.", + "ERROR 0 001 1 000022 000022 015 000022 017 RNF7031 I 00 044 The name or indicator F11 is not referenced.", + "ERROR 0 001 1 000024 000024 015 000024 017 RNF7031 I 00 044 The name or indicator F13 is not referenced.", + "ERROR 0 001 1 000025 000025 015 000025 017 RNF7031 I 00 044 The name or indicator F14 is not referenced.", + "ERROR 0 001 1 000026 000026 015 000026 017 RNF7031 I 00 044 The name or indicator F15 is not referenced.", + "ERROR 0 001 1 000027 000027 015 000027 017 RNF7031 I 00 044 The name or indicator F16 is not referenced.", + "ERROR 0 001 1 000028 000028 015 000028 017 RNF7031 I 00 044 The name or indicator F17 is not referenced.", + "ERROR 0 001 1 000029 000029 015 000029 017 RNF7031 I 00 044 The name or indicator F18 is not referenced.", + "ERROR 0 001 1 000030 000030 015 000030 017 RNF7031 I 00 044 The name or indicator F19 is not referenced.", + "ERROR 0 001 1 000031 000031 015 000031 017 RNF7031 I 00 044 The name or indicator F20 is not referenced.", + "ERROR 0 001 1 000032 000032 015 000032 017 RNF7031 I 00 044 The name or indicator F21 is not referenced.", + "ERROR 0 001 1 000033 000033 015 000033 017 RNF7031 I 00 044 The name or indicator F22 is not referenced.", + "ERROR 0 001 1 000034 000034 015 000034 017 RNF7031 I 00 044 The name or indicator F24 is not referenced.", + "ERROR 0 001 1 000036 000036 015 000036 018 RNF7031 I 00 045 The name or indicator HELP is not referenced.", + "ERROR 0 001 1 000068 000068 007 000068 011 RNF7031 I 00 046 The name or indicator INDEX is not referenced.", + "ERROR 0 001 1 000172 000172 009 000172 014 RNF7031 I 00 047 The name or indicator LCOUNT is not referenced.", + "ERROR 0 001 1 000174 000174 009 000174 015 RNF7031 I 00 048 The name or indicator LONGACT is not referenced.", + "ERROR 0 001 1 000037 000037 015 000037 019 RNF7031 I 00 046 The name or indicator PRINT is not referenced.", + "ERROR 0 001 1 000138 000138 014 000138 019 RNF7031 I 00 047 The name or indicator SQFAPP is not referenced.", + "ERROR 0 001 1 000136 000136 014 000136 019 RNF7031 I 00 047 The name or indicator SQFCRT is not referenced.", + "ERROR 0 001 1 000137 000137 014 000137 019 RNF7031 I 00 047 The name or indicator SQFOVR is not referenced.", + "ERROR 0 001 1 000135 000135 014 000135 018 RNF7031 I 00 046 The name or indicator SQFRD is not referenced.", + "ERROR 0 001 1 000078 000078 011 000078 016 RNF7031 I 00 047 The name or indicator SQLAID is not referenced.", + "ERROR 0 001 1 000080 000080 011 000080 016 RNF7031 I 00 047 The name or indicator SQLABC is not referenced.", + "ERROR 0 001 1 000082 000082 011 000082 016 RNF7031 I 00 047 The name or indicator SQLCOD is not referenced.", + "ERROR 0 001 1 000084 000084 011 000084 016 RNF7031 I 00 047 The name or indicator SQLERL is not referenced.", + "ERROR 0 001 1 000086 000086 011 000086 016 RNF7031 I 00 047 The name or indicator SQLERM is not referenced.", + "ERROR 0 001 1 000088 000088 011 000088 016 RNF7031 I 00 047 The name or indicator SQLERP is not referenced.", + "ERROR 0 001 1 000090 000090 011 000090 016 RNF7031 I 00 047 The name or indicator SQLER1 is not referenced.", + "ERROR 0 001 1 000091 000091 011 000091 016 RNF7031 I 00 047 The name or indicator SQLER2 is not referenced.", + "ERROR 0 001 1 000092 000092 011 000092 016 RNF7031 I 00 047 The name or indicator SQLER3 is not referenced.", + "ERROR 0 001 1 000093 000093 011 000093 016 RNF7031 I 00 047 The name or indicator SQLER4 is not referenced.", + "ERROR 0 001 1 000094 000094 011 000094 016 RNF7031 I 00 047 The name or indicator SQLER5 is not referenced.", + "ERROR 0 001 1 000096 000096 011 000096 017 RNF7031 I 00 048 The name or indicator SQLERRD is not referenced.", + "ERROR 0 001 1 000098 000098 011 000098 016 RNF7031 I 00 047 The name or indicator SQLWN0 is not referenced.", + "ERROR 0 001 1 000099 000099 011 000099 016 RNF7031 I 00 047 The name or indicator SQLWN1 is not referenced.", + "ERROR 0 001 1 000100 000100 011 000100 016 RNF7031 I 00 047 The name or indicator SQLWN2 is not referenced.", + "ERROR 0 001 1 000101 000101 011 000101 016 RNF7031 I 00 047 The name or indicator SQLWN3 is not referenced.", + "ERROR 0 001 1 000102 000102 011 000102 016 RNF7031 I 00 047 The name or indicator SQLWN4 is not referenced.", + "ERROR 0 001 1 000103 000103 011 000103 016 RNF7031 I 00 047 The name or indicator SQLWN5 is not referenced.", + "ERROR 0 001 1 000104 000104 011 000104 016 RNF7031 I 00 047 The name or indicator SQLWN6 is not referenced.", + "ERROR 0 001 1 000105 000105 011 000105 016 RNF7031 I 00 047 The name or indicator SQLWN7 is not referenced.", + "ERROR 0 001 1 000106 000106 011 000106 016 RNF7031 I 00 047 The name or indicator SQLWN8 is not referenced.", + "ERROR 0 001 1 000107 000107 011 000107 016 RNF7031 I 00 047 The name or indicator SQLWN9 is not referenced.", + "ERROR 0 001 1 000108 000108 011 000108 016 RNF7031 I 00 047 The name or indicator SQLWNA is not referenced.", + "ERROR 0 001 1 000109 000109 011 000109 017 RNF7031 I 00 048 The name or indicator SQLWARN is not referenced.", + "ERROR 0 001 1 000111 000111 011 000111 016 RNF7031 I 00 047 The name or indicator SQLSTT is not referenced.", + "ERROR 0 001 1 000126 000126 015 000126 026 RNF7031 I 00 051 The name or indicator SQLCMIT... is not referenced.", + "ERROR 0 001 1 000049 000049 003 000049 012 RNF7031 I 00 051 The name or indicator PROCESSSCF is not referenced.", + "ERROR 0 001 1 000050 000050 003 000050 012 RNF7031 I 00 051 The name or indicator REPRINTSCF is not referenced.", + "ERROR 0 001 1 000051 000051 003 000051 007 RNF7031 I 00 046 The name or indicator ERROR is not referenced.", + "ERROR 0 001 1 000052 000052 003 000052 010 RNF7031 I 00 049 The name or indicator PAGEDOWN is not referenced.", + "ERROR 0 001 1 000053 000053 003 000053 008 RNF7031 I 00 047 The name or indicator PAGEUP is not referenced.", + "ERROR 0 001 1 000054 000054 003 000054 008 RNF7031 I 00 047 The name or indicator SFLEND is not referenced.", + "ERROR 0 001 1 000055 000055 003 000055 010 RNF7031 I 00 049 The name or indicator SFLBEGIN is not referenced.", + "ERROR 0 001 1 000056 000056 003 000056 010 RNF7031 I 00 049 The name or indicator NORECORD is not referenced.", + "ERROR 0 001 1 000058 000058 003 000058 008 RNF7031 I 00 047 The name or indicator SFLCLR is not referenced.", + "FILEEND 0 001 000305" + ]; + + const errors = parseErrors(lines); + + // 2 files (one main and one copybook) + const filePath = `/home/LINUX/builds/ibmi-company_system-rmake/qrpglesrc/employees.pgm.sqlrpgle`; + const copybook_file_path = `/home/LINUX/builds/ibmi-company_system-rmake/qrpgleref/constants.rpgleinc`; + expect(errors.size).toBe(2); + expect(errors.has(filePath)).toBe(true); + expect(errors.has(copybook_file_path)).toBe(true); + + // main file errors + const fileErrors = errors.get(filePath); + expect(fileErrors).toBeDefined(); + expect(fileErrors?.length).toBe(25); + + // copybook file errors + const copybook_fileErrors = errors.get(copybook_file_path); + expect(copybook_fileErrors).toBeDefined(); + expect(copybook_fileErrors?.length).toBe(24); + }); +}); + diff --git a/src/testing/ileErrors.ts b/src/testing/ileErrors.ts deleted file mode 100644 index 84f6fe313..000000000 --- a/src/testing/ileErrors.ts +++ /dev/null @@ -1,410 +0,0 @@ -import assert from "assert"; -import { TestSuite } from "."; -import { parseErrors } from "../api/errors/parser"; - -export const ILEErrorSuite: TestSuite = { - name: `ILE Error API tests`, - tests: [ - { - name: `Basic test (CRTSQLRPGI, member)`, test: async () => { - const lines = [ - `TIMESTAMP 0 20230524115628`, - `PROCESSOR 0 999 1`, - `FILEID 0 999 000000 026 QTEMP/QSQLTEMP1(EMPLOYEES) 20230524115628 0`, - `FILEID 0 001 000000 026 LIAMA/QRPGLESRC(EMPLOYEES) 20230516152429 0`, - `ERROR 0 001 1 000044 000044 000 000044 000 SQL1001 S 30 048 External file definition for EMPLOYEE not found.`, - `ERROR 0 001 1 000093 000093 020 000093 020 SQL1103 W 10 069 Position 20 Column definitions for table EMPLOYEE in *LIBL not found.`, - `ERROR 0 001 1 000103 000103 019 000103 019 SQL0312 S 30 212 Position 19 Variable EMPLOYEE not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, - `ERROR 0 001 1 000103 000103 028 000103 028 SQL0312 S 30 209 Position 28 Variable EMPNO not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, - `ERROR 0 001 1 000104 000104 016 000104 016 SQL0312 S 30 212 Position 16 Variable EMPLOYEE not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, - `ERROR 0 001 1 000104 000104 025 000104 025 SQL0312 S 30 212 Position 25 Variable FIRSTNME not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, - `ERROR 0 001 1 000105 000105 016 000105 016 SQL0312 S 30 212 Position 16 Variable EMPLOYEE not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, - `ERROR 0 001 1 000105 000105 025 000105 025 SQL0312 S 30 212 Position 25 Variable LASTNAME not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, - `ERROR 0 001 1 000106 000106 016 000106 016 SQL0312 S 30 212 Position 16 Variable EMPLOYEE not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, - `ERROR 0 001 1 000106 000106 025 000106 025 SQL0312 S 30 207 Position 25 Variable JOB not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, - `EXPANSION 0 001 000000 000000 999 000049 000113`, - `EXPANSION 0 001 000096 000096 999 000154 000171`, - `EXPANSION 0 001 000096 000096 999 000180 000193`, - `EXPANSION 0 001 000106 000106 999 000204 000207`, - `EXPANSION 0 001 000121 000121 999 000223 000234`, - `FILEEND 0 001 000151`, - `FILEEND 0 999 000264` - ]; - - const errors = parseErrors(lines); - - const filePath = `LIAMA/QRPGLESRC/EMPLOYEES`; - - assert.strictEqual(errors.size, 1); - assert.strictEqual(errors.has(filePath), true); - - const fileErrors = errors.get(filePath); - assert.notStrictEqual(fileErrors, undefined); - assert.strictEqual(fileErrors?.length, 10); - - const errorA = fileErrors.find(err => err.lineNum === 44); - assert.notStrictEqual(errorA, undefined); - - assert.strictEqual(errorA?.code, `SQL1001`); - assert.strictEqual(errorA?.lineNum, 44); - assert.strictEqual(errorA?.toLineNum, 44); - assert.strictEqual(errorA?.column, 0); - assert.strictEqual(errorA?.toColumn, 0); - assert.strictEqual(errorA?.sev, 30); - assert.strictEqual(errorA?.text, `External file definition for EMPLOYEE not found.`); - - const lineErrors = fileErrors.filter(err => err.lineNum === 104); - assert.strictEqual(lineErrors.length, 2); - - assert.strictEqual(lineErrors[0]?.code, `SQL0312`); - assert.strictEqual(lineErrors[0]?.lineNum, 104); - assert.strictEqual(lineErrors[0]?.toLineNum, 104); - assert.strictEqual(lineErrors[0]?.column, 16); - assert.strictEqual(lineErrors[0]?.toColumn, 16); - assert.strictEqual(lineErrors[0]?.sev, 30); - assert.strictEqual(lineErrors[0]?.text, `Position 16 Variable EMPLOYEE not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`); - - assert.strictEqual(lineErrors[1]?.code, `SQL0312`); - assert.strictEqual(lineErrors[1]?.lineNum, 104); - assert.strictEqual(lineErrors[1]?.toLineNum, 104); - assert.strictEqual(lineErrors[1]?.column, 25); - assert.strictEqual(lineErrors[1]?.toColumn, 25); - assert.strictEqual(lineErrors[1]?.sev, 30); - assert.strictEqual(lineErrors[1]?.text, `Position 25 Variable FIRSTNME not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`); - } - }, - - { - name: `Basic test (CRTSQLRPGI, streamfile)`, test: async () => { - const lines = [ - `TIMESTAMP 0 20230524122108`, - `PROCESSOR 0 999 1`, - `FILEID 0 999 000000 026 QTEMP/QSQLTEMP1(EMPLOYEES) 20230524122108 0`, - `FILEID 0 001 000000 071 /home/LINUX/builds/ibmi-company_system/qrpglesrc/employees.pgm.sqlrpgle 20230429182220 0`, - `ERROR 0 001 1 000041 000041 000 000041 000 SQL1001 S 30 048 External file definition for EMPLOYEE not found.`, - `ERROR 0 001 1 000095 000095 020 000095 020 SQL1103 W 10 069 Position 20 Column definitions for table EMPLOYEE in *LIBL not found.`, - `ERROR 0 001 1 000105 000105 025 000105 025 SQL0312 S 30 212 Position 25 Variable EMPLOYEE not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, - `ERROR 0 001 1 000105 000105 034 000105 034 SQL0312 S 30 209 Position 34 Variable EMPNO not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, - `ERROR 0 001 1 000106 000106 025 000106 025 SQL0312 S 30 212 Position 25 Variable EMPLOYEE not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, - `ERROR 0 001 1 000106 000106 034 000106 034 SQL0312 S 30 212 Position 34 Variable FIRSTNME not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, - `ERROR 0 001 1 000107 000107 025 000107 025 SQL0312 S 30 212 Position 25 Variable EMPLOYEE not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, - `ERROR 0 001 1 000107 000107 034 000107 034 SQL0312 S 30 212 Position 34 Variable LASTNAME not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, - `ERROR 0 001 1 000108 000108 025 000108 025 SQL0312 S 30 212 Position 25 Variable EMPLOYEE not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, - `ERROR 0 001 1 000108 000108 034 000108 034 SQL0312 S 30 207 Position 34 Variable JOB not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`, - `EXPANSION 0 001 000000 000000 999 000051 000115`, - `EXPANSION 0 001 000098 000098 999 000154 000171`, - `EXPANSION 0 001 000098 000098 999 000182 000195`, - `EXPANSION 0 001 000108 000108 999 000206 000209`, - `EXPANSION 0 001 000123 000123 999 000225 000236`, - `FILEEND 0 001 000153`, - `FILEEND 0 999 000266` - ]; - - const errors = parseErrors(lines); - - const filePath = `/home/LINUX/builds/ibmi-company_system/qrpglesrc/employees.pgm.sqlrpgle`; - - assert.strictEqual(errors.size, 1); - assert.strictEqual(errors.has(filePath), true); - - const fileErrors = errors.get(filePath); - assert.notStrictEqual(fileErrors, undefined); - assert.strictEqual(fileErrors?.length, 10); - - const errorA = fileErrors.find(err => err.lineNum === 41); - assert.notStrictEqual(errorA, undefined); - - assert.strictEqual(errorA?.code, `SQL1001`); - assert.strictEqual(errorA?.lineNum, 41); - assert.strictEqual(errorA?.toLineNum, 41); - assert.strictEqual(errorA?.column, 0); - assert.strictEqual(errorA?.toColumn, 0); - assert.strictEqual(errorA?.sev, 30); - assert.strictEqual(errorA?.text, `External file definition for EMPLOYEE not found.`); - - const lineErrors = fileErrors.filter(err => err.lineNum === 106); - assert.strictEqual(lineErrors.length, 2); - - assert.strictEqual(lineErrors[0]?.code, `SQL0312`); - assert.strictEqual(lineErrors[0]?.lineNum, 106); - assert.strictEqual(lineErrors[0]?.toLineNum, 106); - assert.strictEqual(lineErrors[0]?.column, 25); - assert.strictEqual(lineErrors[0]?.toColumn, 25); - assert.strictEqual(lineErrors[0]?.sev, 30); - assert.strictEqual(lineErrors[0]?.text, `Position 25 Variable EMPLOYEE not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`); - - assert.strictEqual(lineErrors[1]?.code, `SQL0312`); - assert.strictEqual(lineErrors[1]?.lineNum, 106); - assert.strictEqual(lineErrors[1]?.toLineNum, 106); - assert.strictEqual(lineErrors[1]?.column, 34); - assert.strictEqual(lineErrors[1]?.toColumn, 34); - assert.strictEqual(lineErrors[1]?.sev, 30); - assert.strictEqual(lineErrors[1]?.text, `Position 34 Variable FIRSTNME not defined or not usable. Reason: No declaration for the variable exists, the declaration is not within the current scope, or the variable does not have an equivalent SQL data type.`); - } - }, - - { - name: `Long source file path containing whitespaces (CRTSQLRPGI, streamfile)`, test: async () => { - const lines = [ - `TIMESTAMP 0 20230405035632`, - `PROCESSOR 0 999 1`, - `FILEID 0 999 000000 024 QTEMP/QSQLTEMP1(FIX1200) 20230405035632 0`, - `FILEID 0 001 000000 646 /home/ANGELORPA/builds/sources/long-directory-name-for-testing-long-paths/subdirectory-with-a-long-name-for-testing-long-paths/another-subdirectory-with-a-long-name-for-testing-long-paths/one-more-subdirectory-this-is-the-last-one/01-long directory name w`, - `FILEIDCONT 0 001 000000 000 ith spaces in/02-long directory with space in his name/03-long directory name with space in for testing prupouse/04-long directory name with space in for testing event file parser/05-long directory name with space in for testing event file parser/06-long `, - `FILEIDCONT 0 001 000000 000 directory name with space in for testing event file parser/sorce file long name with space in for testing event file parser.pmg.sqlrpgle 20230403024018 0`, - `ERROR 0 999 2 000000 000000 000 000000 000 SQL0053 W 10 024 No SQL statements found.`, - `EXPANSION 0 001 000000 000000 999 000006 000070`, - `FILEEND 0 001 000009`, - `FILEEND 0 999 000074`, - `PROCESSOR 0 000 1`, - `FILEID 0 001 000000 046 /QSYS.LIB/QTEMP.LIB/QSQLTEMP1.FILE/FIX1200.MBR 20230405035632 0`, - `ERROR 0 001 1 000072 000072 001 000072 005 RNF5377 E 20 038 The end of the expression is expected.`, - `ERROR 0 001 1 000072 000072 001 000072 005 RNF7030 S 30 043 The name or indicator DSPLY is not defined.`, - `ERROR 0 001 1 000070 000070 014 000070 019 RNF7031 I 00 047 The name or indicator SQFAPP is not referenced.`, - `ERROR 0 001 1 000068 000068 014 000068 019 RNF7031 I 00 047 The name or indicator SQFCRT is not referenced.`, - `ERROR 0 001 1 000069 000069 014 000069 019 RNF7031 I 00 047 The name or indicator SQFOVR is not referenced.`, - `ERROR 0 001 1 000067 000067 014 000067 018 RNF7031 I 00 046 The name or indicator SQFRD is not referenced.`, - `ERROR 0 001 1 000010 000010 011 000010 016 RNF7031 I 00 047 The name or indicator SQLAID is not referenced.`, - `ERROR 0 001 1 000012 000012 011 000012 016 RNF7031 I 00 047 The name or indicator SQLABC is not referenced.`, - `ERROR 0 001 1 000014 000014 011 000014 016 RNF7031 I 00 047 The name or indicator SQLCOD is not referenced.`, - `ERROR 0 001 1 000016 000016 011 000016 016 RNF7031 I 00 047 The name or indicator SQLERL is not referenced.`, - `ERROR 0 001 1 000018 000018 011 000018 016 RNF7031 I 00 047 The name or indicator SQLERM is not referenced.`, - `ERROR 0 001 1 000020 000020 011 000020 016 RNF7031 I 00 047 The name or indicator SQLERP is not referenced.`, - `ERROR 0 001 1 000022 000022 011 000022 016 RNF7031 I 00 047 The name or indicator SQLER1 is not referenced.`, - `ERROR 0 001 1 000023 000023 011 000023 016 RNF7031 I 00 047 The name or indicator SQLER2 is not referenced.`, - `ERROR 0 001 1 000024 000024 011 000024 016 RNF7031 I 00 047 The name or indicator SQLER3 is not referenced.`, - `ERROR 0 001 1 000025 000025 011 000025 016 RNF7031 I 00 047 The name or indicator SQLER4 is not referenced.`, - `ERROR 0 001 1 000026 000026 011 000026 016 RNF7031 I 00 047 The name or indicator SQLER5 is not referenced.`, - `ERROR 0 001 1 000027 000027 011 000027 016 RNF7031 I 00 047 The name or indicator SQLER6 is not referenced.`, - `ERROR 0 001 1 000028 000028 011 000028 017 RNF7031 I 00 048 The name or indicator SQLERRD is not referenced.`, - `ERROR 0 001 1 000030 000030 011 000030 016 RNF7031 I 00 047 The name or indicator SQLWN0 is not referenced.`, - `ERROR 0 001 1 000031 000031 011 000031 016 RNF7031 I 00 047 The name or indicator SQLWN1 is not referenced.`, - `ERROR 0 001 1 000032 000032 011 000032 016 RNF7031 I 00 047 The name or indicator SQLWN2 is not referenced.`, - `ERROR 0 001 1 000033 000033 011 000033 016 RNF7031 I 00 047 The name or indicator SQLWN3 is not referenced.`, - `ERROR 0 001 1 000034 000034 011 000034 016 RNF7031 I 00 047 The name or indicator SQLWN4 is not referenced.`, - `ERROR 0 001 1 000035 000035 011 000035 016 RNF7031 I 00 047 The name or indicator SQLWN5 is not referenced.`, - `ERROR 0 001 1 000036 000036 011 000036 016 RNF7031 I 00 047 The name or indicator SQLWN6 is not referenced.`, - `ERROR 0 001 1 000037 000037 011 000037 016 RNF7031 I 00 047 The name or indicator SQLWN7 is not referenced.`, - `ERROR 0 001 1 000038 000038 011 000038 016 RNF7031 I 00 047 The name or indicator SQLWN8 is not referenced.`, - `ERROR 0 001 1 000039 000039 011 000039 016 RNF7031 I 00 047 The name or indicator SQLWN9 is not referenced.`, - `ERROR 0 001 1 000040 000040 011 000040 016 RNF7031 I 00 047 The name or indicator SQLWNA is not referenced.`, - `ERROR 0 001 1 000041 000041 011 000041 017 RNF7031 I 00 048 The name or indicator SQLWARN is not referenced.`, - `ERROR 0 001 1 000043 000043 011 000043 016 RNF7031 I 00 047 The name or indicator SQLSTT is not referenced.`, - `ERROR 0 001 1 000054 000054 015 000054 026 RNF7031 I 00 051 The name or indicator SQLCLSE... is not referenced.`, - `ERROR 0 001 1 000058 000058 015 000058 026 RNF7031 I 00 051 The name or indicator SQLCMIT... is not referenced.`, - `ERROR 0 001 1 000050 000050 015 000050 026 RNF7031 I 00 051 The name or indicator SQLOPEN... is not referenced.`, - `ERROR 0 001 1 000045 000045 015 000045 027 RNF7031 I 00 051 The name or indicator SQLROUT... is not referenced.`, - `ERROR 0 001 0 000000 000000 000 000000 000 RNS9308 T 50 057 Compilation stopped. Severity 30 errors found in program.`, - `FILEEND 0 001 000074` - ]; - - const errors = parseErrors(lines); - - // path containing whitespaces - const filePath = `/home/ANGELORPA/builds/sources/long-directory-name-for-testing-long-paths/subdirectory-with-a-long` + - `-name-for-testing-long-paths/another-subdirectory-with-a-long-name-for-testing-long-paths/one-more-subdirectory-this` + - `-is-the-last-one/01-long directory name with spaces in/02-long directory with space in his name/03-long directory name` + - ` with space in for testing prupouse/04-long directory name with space in for testing event file parser/05-long directory` + - ` name with space in for testing event file parser/06-long directory name with space in for testing event file parser/` + - `sorce file long name with space in for testing event file parser.pmg.sqlrpgle`; - - // erros.size is equal 1 as even the error in the intermediate file can be mapped to the original source file - assert.strictEqual(errors.size, 1); - assert.strictEqual(errors.has(filePath), true); - } - }, - { - name: `Nested Copybook (CRTRPGLE, streamfile)`, test: async () => { - const lines = [ - `TIMESTAMP 0 20230619181512`, - `PROCESSOR 0 000 1`, - `FILEID 0 001 000000 060 /home/ANGELORPA/builds/fix1200/display/qrpglesrc/hello.rpgle 20230619181454 0`, - `FILEID 0 002 000004 063 /home/ANGELORPA/builds/fix1200/display/qprotsrc/constants.rpgle 20230619180115 0`, - `FILEID 0 003 000007 064 /home/ANGELORPA/builds/fix1200/display/qprotsrc/constLeve2.rpgle 20230619181501 0`, - `ERROR 0 003 1 000004 000004 002 000004 002 RNF0734 S 30 052 The statement must be complete before the file ends.`, - `FILEEND 0 003 000004`, - `ERROR 0 003 1 000004 000004 002 000004 002 RNF0734 S 30 052 The statement must be complete before the file ends.`, - `FILEEND 0 002 000007`, - `ERROR 0 001 1 000006 000006 001 000006 005 RNF3312 E 20 075 A keyword is specified more than once for a definition; keyword is ignored.`, - `ERROR 0 001 1 000006 000006 007 000006 011 RNF3312 E 20 075 A keyword is specified more than once for a definition; keyword is ignored.`, - `ERROR 0 001 1 000006 000006 013 000006 025 RNF3312 E 20 075 A keyword is specified more than once for a definition; keyword is ignored.`, - `ERROR 0 001 1 000012 000012 001 000012 001 RNF0637 S 30 068 An operand was expected but was not found; specification is ignored.`, - `ERROR 0 001 1 000012 000012 007 000012 007 RNF0637 S 30 068 An operand was expected but was not found; specification is ignored.`, - `ERROR 0 003 1 000003 000003 007 000003 010 RNF7031 I 00 045 The name or indicator FILE is not referenced.`, - `ERROR 0 002 1 000003 000003 007 000003 015 RNF7031 I 00 050 The name or indicator FIRST_DAY is not referenced.`, - `ERROR 0 001 1 000012 000012 002 000012 005 RNF7030 S 30 042 The name or indicator INLR is not defined.`, - `ERROR 0 002 1 000004 000004 007 000004 016 RNF7031 I 00 051 The name or indicator SECOND_DAY is not referenced.`, - `ERROR 0 001 0 000000 000000 000 000000 000 RNS9308 T 50 057 Compilation stopped.Severity 30 errors found in program.`, - `FILEEND 0 001 000013` - ] - - - const errors = parseErrors(lines); - - // 3 files (one main, one copybook and a nested copybook) - const filePath = `/home/ANGELORPA/builds/fix1200/display/qrpglesrc/hello.rpgle`; - const copybook_file_path = `/home/ANGELORPA/builds/fix1200/display/qprotsrc/constants.rpgle`; - const nested_copybook_file_path = `/home/ANGELORPA/builds/fix1200/display/qprotsrc/constLeve2.rpgle`; - - // erros.size is equal to the number of PROCESSOR records in the events file - assert.strictEqual(errors.size, 3); - - // should be 3 diferents files paths - assert.strictEqual(errors.has(filePath), true); - assert.strictEqual(errors.has(copybook_file_path), true); - assert.strictEqual(errors.has(nested_copybook_file_path), true); - - // main file errors - const fileErrors = errors.get(filePath); - assert.notStrictEqual(fileErrors, undefined); - assert.strictEqual(fileErrors?.length, 7); - - // copybook file errors - const copybook_fileErrors = errors.get(copybook_file_path); - assert.notStrictEqual(copybook_fileErrors, undefined); - assert.strictEqual(copybook_fileErrors?.length, 2); - - // nested copybook file errors - const nested_copybook_fileErrors = errors.get(nested_copybook_file_path); - assert.notStrictEqual(nested_copybook_fileErrors, undefined); - assert.strictEqual(nested_copybook_fileErrors?.length, 3); - } - }, - { - name: `Filter errors (CRTSQLRPGI, streamfile)`, test: async () => { - const lines = [ - "TIMESTAMP 0 20230815094609", - "PROCESSOR 0 999 1", - "FILEID 0 999 000000 024 QTEMP/QSQLPRE(EMPLOYEES) 20230815094609 0", - "FILEID 0 001 000000 077 /home/LINUX/builds/ibmi-company_system-rmake/qrpglesrc/employees.pgm.sqlrpgle 20230805131228 0", - "FILEID 0 002 000010 073 /home/LINUX/builds/ibmi-company_system-rmake/qrpgleref/constants.rpgleinc 20230804103719 0", - "EXPANSION 0 000 000000 000000 999 000011 000011", - "FILEEND 0 002 000026", - "EXPANSION 0 000 000000 000000 999 000038 000038", - "FILEEND 0 001 000145", - "FILEEND 0 999 000173", - "PROCESSOR 0 999 1", - "FILEID 0 999 000000 026 QTEMP/QSQLTEMP1(EMPLOYEES) 20230815094609 0", - "FILEID 0 001 000000 024 QTEMP/QSQLPRE(EMPLOYEES) 20230815094609 0", - "EXPANSION 0 001 000000 000000 999 000074 000138", - "EXPANSION 0 001 000118 000118 999 000176 000205", - "EXPANSION 0 001 000118 000118 999 000214 000227", - "EXPANSION 0 001 000128 000128 999 000238 000248", - "EXPANSION 0 001 000143 000143 999 000264 000275", - "FILEEND 0 001 000173", - "FILEEND 0 999 000305", - "PROCESSOR 0 000 1", - "FILEID 0 001 000000 048 /QSYS.LIB/QTEMP.LIB/QSQLTEMP1.FILE/EMPLOYEES.MBR 20230815094609 0", - "ERROR 0 001 1 000200 000200 009 000200 017 RNF7031 I 00 050 The name or indicator SQL_00016 is not referenced.", - "ERROR 0 001 1 000202 000202 009 000202 017 RNF7031 I 00 050 The name or indicator SQL_00018 is not referenced.", - "ERROR 0 001 1 000203 000203 009 000203 017 RNF7031 I 00 050 The name or indicator SQL_00019 is not referenced.", - "ERROR 0 001 1 000204 000204 009 000204 017 RNF7031 I 00 050 The name or indicator SQL_00020 is not referenced.", - "ERROR 0 001 1 000188 000188 009 000188 017 RNF7031 I 00 050 The name or indicator SQL_00007 is not referenced.", - "ERROR 0 001 1 000189 000189 009 000189 017 RNF7031 I 00 050 The name or indicator SQL_00008 is not referenced.", - "ERROR 0 001 1 000191 000191 009 000191 017 RNF7031 I 00 050 The name or indicator SQL_00010 is not referenced.", - "ERROR 0 001 1 000179 000179 009 000179 017 RNF7031 I 00 050 The name or indicator SQL_00001 is not referenced.", - "ERROR 0 001 1 000182 000182 009 000182 017 RNF7031 I 00 050 The name or indicator SQL_00004 is not referenced.", - "ERROR 0 001 1 000173 000173 009 000173 014 RNF7031 I 00 047 The name or indicator ACTION is not referenced.", - "ERROR 0 001 1 000070 000070 008 000070 015 RNF7031 I 00 048 The name or indicator MIDINIT is not referenced.", - "ERROR 0 001 1 000070 000070 008 000070 015 RNF7031 I 00 049 The name or indicator WORKDEPT is not referenced.", - "ERROR 0 001 1 000070 000070 008 000070 015 RNF7031 I 00 048 The name or indicator PHONENO is not referenced.", - "ERROR 0 001 1 000070 000070 008 000070 015 RNF7031 I 00 049 The name or indicator HIREDATE is not referenced.", - "ERROR 0 001 1 000070 000070 008 000070 015 RNF7031 I 00 048 The name or indicator EDLEVEL is not referenced.", - "ERROR 0 001 1 000070 000070 008 000070 015 RNF7031 I 00 044 The name or indicator SEX is not referenced.", - "ERROR 0 001 1 000070 000070 008 000070 015 RNF7031 I 00 050 The name or indicator BIRTHDATE is not referenced.", - "ERROR 0 001 1 000070 000070 008 000070 015 RNF7031 I 00 047 The name or indicator SALARY is not referenced.", - "ERROR 0 001 1 000070 000070 008 000070 015 RNF7031 I 00 046 The name or indicator BONUS is not referenced.", - "ERROR 0 001 1 000070 000070 008 000070 015 RNF7031 I 00 045 The name or indicator COMM is not referenced.", - "ERROR 0 001 1 000004 000004 008 000004 016 RNF7031 I 00 050 The name or indicator EMPLOYEES is not referenced.", - "ERROR 0 001 1 000042 000042 001 000042 000 RNF7089 I 00 051 RPG provides Separate-Indicator area for file EMPS.", - "ERROR 0 001 1 000012 000012 015 000012 017 RNF7031 I 00 044 The name or indicator F01 is not referenced.", - "ERROR 0 001 1 000013 000013 015 000013 017 RNF7031 I 00 044 The name or indicator F02 is not referenced.", - "ERROR 0 001 1 000014 000014 015 000014 017 RNF7031 I 00 044 The name or indicator F03 is not referenced.", - "ERROR 0 001 1 000015 000015 015 000015 017 RNF7031 I 00 044 The name or indicator F04 is not referenced.", - "ERROR 0 001 1 000016 000016 015 000016 017 RNF7031 I 00 044 The name or indicator F05 is not referenced.", - "ERROR 0 001 1 000017 000017 015 000017 017 RNF7031 I 00 044 The name or indicator F06 is not referenced.", - "ERROR 0 001 1 000018 000018 015 000018 017 RNF7031 I 00 044 The name or indicator F07 is not referenced.", - "ERROR 0 001 1 000019 000019 015 000019 017 RNF7031 I 00 044 The name or indicator F08 is not referenced.", - "ERROR 0 001 1 000020 000020 015 000020 017 RNF7031 I 00 044 The name or indicator F09 is not referenced.", - "ERROR 0 001 1 000021 000021 015 000021 017 RNF7031 I 00 044 The name or indicator F10 is not referenced.", - "ERROR 0 001 1 000022 000022 015 000022 017 RNF7031 I 00 044 The name or indicator F11 is not referenced.", - "ERROR 0 001 1 000024 000024 015 000024 017 RNF7031 I 00 044 The name or indicator F13 is not referenced.", - "ERROR 0 001 1 000025 000025 015 000025 017 RNF7031 I 00 044 The name or indicator F14 is not referenced.", - "ERROR 0 001 1 000026 000026 015 000026 017 RNF7031 I 00 044 The name or indicator F15 is not referenced.", - "ERROR 0 001 1 000027 000027 015 000027 017 RNF7031 I 00 044 The name or indicator F16 is not referenced.", - "ERROR 0 001 1 000028 000028 015 000028 017 RNF7031 I 00 044 The name or indicator F17 is not referenced.", - "ERROR 0 001 1 000029 000029 015 000029 017 RNF7031 I 00 044 The name or indicator F18 is not referenced.", - "ERROR 0 001 1 000030 000030 015 000030 017 RNF7031 I 00 044 The name or indicator F19 is not referenced.", - "ERROR 0 001 1 000031 000031 015 000031 017 RNF7031 I 00 044 The name or indicator F20 is not referenced.", - "ERROR 0 001 1 000032 000032 015 000032 017 RNF7031 I 00 044 The name or indicator F21 is not referenced.", - "ERROR 0 001 1 000033 000033 015 000033 017 RNF7031 I 00 044 The name or indicator F22 is not referenced.", - "ERROR 0 001 1 000034 000034 015 000034 017 RNF7031 I 00 044 The name or indicator F24 is not referenced.", - "ERROR 0 001 1 000036 000036 015 000036 018 RNF7031 I 00 045 The name or indicator HELP is not referenced.", - "ERROR 0 001 1 000068 000068 007 000068 011 RNF7031 I 00 046 The name or indicator INDEX is not referenced.", - "ERROR 0 001 1 000172 000172 009 000172 014 RNF7031 I 00 047 The name or indicator LCOUNT is not referenced.", - "ERROR 0 001 1 000174 000174 009 000174 015 RNF7031 I 00 048 The name or indicator LONGACT is not referenced.", - "ERROR 0 001 1 000037 000037 015 000037 019 RNF7031 I 00 046 The name or indicator PRINT is not referenced.", - "ERROR 0 001 1 000138 000138 014 000138 019 RNF7031 I 00 047 The name or indicator SQFAPP is not referenced.", - "ERROR 0 001 1 000136 000136 014 000136 019 RNF7031 I 00 047 The name or indicator SQFCRT is not referenced.", - "ERROR 0 001 1 000137 000137 014 000137 019 RNF7031 I 00 047 The name or indicator SQFOVR is not referenced.", - "ERROR 0 001 1 000135 000135 014 000135 018 RNF7031 I 00 046 The name or indicator SQFRD is not referenced.", - "ERROR 0 001 1 000078 000078 011 000078 016 RNF7031 I 00 047 The name or indicator SQLAID is not referenced.", - "ERROR 0 001 1 000080 000080 011 000080 016 RNF7031 I 00 047 The name or indicator SQLABC is not referenced.", - "ERROR 0 001 1 000082 000082 011 000082 016 RNF7031 I 00 047 The name or indicator SQLCOD is not referenced.", - "ERROR 0 001 1 000084 000084 011 000084 016 RNF7031 I 00 047 The name or indicator SQLERL is not referenced.", - "ERROR 0 001 1 000086 000086 011 000086 016 RNF7031 I 00 047 The name or indicator SQLERM is not referenced.", - "ERROR 0 001 1 000088 000088 011 000088 016 RNF7031 I 00 047 The name or indicator SQLERP is not referenced.", - "ERROR 0 001 1 000090 000090 011 000090 016 RNF7031 I 00 047 The name or indicator SQLER1 is not referenced.", - "ERROR 0 001 1 000091 000091 011 000091 016 RNF7031 I 00 047 The name or indicator SQLER2 is not referenced.", - "ERROR 0 001 1 000092 000092 011 000092 016 RNF7031 I 00 047 The name or indicator SQLER3 is not referenced.", - "ERROR 0 001 1 000093 000093 011 000093 016 RNF7031 I 00 047 The name or indicator SQLER4 is not referenced.", - "ERROR 0 001 1 000094 000094 011 000094 016 RNF7031 I 00 047 The name or indicator SQLER5 is not referenced.", - "ERROR 0 001 1 000096 000096 011 000096 017 RNF7031 I 00 048 The name or indicator SQLERRD is not referenced.", - "ERROR 0 001 1 000098 000098 011 000098 016 RNF7031 I 00 047 The name or indicator SQLWN0 is not referenced.", - "ERROR 0 001 1 000099 000099 011 000099 016 RNF7031 I 00 047 The name or indicator SQLWN1 is not referenced.", - "ERROR 0 001 1 000100 000100 011 000100 016 RNF7031 I 00 047 The name or indicator SQLWN2 is not referenced.", - "ERROR 0 001 1 000101 000101 011 000101 016 RNF7031 I 00 047 The name or indicator SQLWN3 is not referenced.", - "ERROR 0 001 1 000102 000102 011 000102 016 RNF7031 I 00 047 The name or indicator SQLWN4 is not referenced.", - "ERROR 0 001 1 000103 000103 011 000103 016 RNF7031 I 00 047 The name or indicator SQLWN5 is not referenced.", - "ERROR 0 001 1 000104 000104 011 000104 016 RNF7031 I 00 047 The name or indicator SQLWN6 is not referenced.", - "ERROR 0 001 1 000105 000105 011 000105 016 RNF7031 I 00 047 The name or indicator SQLWN7 is not referenced.", - "ERROR 0 001 1 000106 000106 011 000106 016 RNF7031 I 00 047 The name or indicator SQLWN8 is not referenced.", - "ERROR 0 001 1 000107 000107 011 000107 016 RNF7031 I 00 047 The name or indicator SQLWN9 is not referenced.", - "ERROR 0 001 1 000108 000108 011 000108 016 RNF7031 I 00 047 The name or indicator SQLWNA is not referenced.", - "ERROR 0 001 1 000109 000109 011 000109 017 RNF7031 I 00 048 The name or indicator SQLWARN is not referenced.", - "ERROR 0 001 1 000111 000111 011 000111 016 RNF7031 I 00 047 The name or indicator SQLSTT is not referenced.", - "ERROR 0 001 1 000126 000126 015 000126 026 RNF7031 I 00 051 The name or indicator SQLCMIT... is not referenced.", - "ERROR 0 001 1 000049 000049 003 000049 012 RNF7031 I 00 051 The name or indicator PROCESSSCF is not referenced.", - "ERROR 0 001 1 000050 000050 003 000050 012 RNF7031 I 00 051 The name or indicator REPRINTSCF is not referenced.", - "ERROR 0 001 1 000051 000051 003 000051 007 RNF7031 I 00 046 The name or indicator ERROR is not referenced.", - "ERROR 0 001 1 000052 000052 003 000052 010 RNF7031 I 00 049 The name or indicator PAGEDOWN is not referenced.", - "ERROR 0 001 1 000053 000053 003 000053 008 RNF7031 I 00 047 The name or indicator PAGEUP is not referenced.", - "ERROR 0 001 1 000054 000054 003 000054 008 RNF7031 I 00 047 The name or indicator SFLEND is not referenced.", - "ERROR 0 001 1 000055 000055 003 000055 010 RNF7031 I 00 049 The name or indicator SFLBEGIN is not referenced.", - "ERROR 0 001 1 000056 000056 003 000056 010 RNF7031 I 00 049 The name or indicator NORECORD is not referenced.", - "ERROR 0 001 1 000058 000058 003 000058 008 RNF7031 I 00 047 The name or indicator SFLCLR is not referenced.", - "FILEEND 0 001 000305" - ]; - - const errors = parseErrors(lines); - - // 2 files (one main and one copybook) - const filePath = `/home/LINUX/builds/ibmi-company_system-rmake/qrpglesrc/employees.pgm.sqlrpgle`; - const copybook_file_path = `/home/LINUX/builds/ibmi-company_system-rmake/qrpgleref/constants.rpgleinc`; - assert.strictEqual(errors.size, 2); - assert.strictEqual(errors.has(filePath), true); - assert.strictEqual(errors.has(copybook_file_path), true); - - // main file errors - const fileErrors = errors.get(filePath); - assert.notStrictEqual(fileErrors, undefined); - assert.strictEqual(fileErrors?.length, 25); - - // copybook file errors - const copybook_fileErrors = errors.get(copybook_file_path); - assert.notStrictEqual(copybook_fileErrors, undefined); - assert.strictEqual(copybook_fileErrors?.length, 24); - } - } - ] -} diff --git a/src/testing/index.ts b/src/testing/index.ts index 31409f459..37faf5142 100644 --- a/src/testing/index.ts +++ b/src/testing/index.ts @@ -5,7 +5,6 @@ import { ActionSuite } from "./action"; import { ContentSuite } from "./content"; import { DeployToolsSuite } from "./deployTools"; import { EncodingSuite } from "./encoding"; -import { ILEErrorSuite } from "./ileErrors"; import { SearchSuite } from "./search"; import { StorageSuite } from "./storage"; import { TestSuitesTreeProvider } from "./testCasesTree"; @@ -17,7 +16,6 @@ const suites: TestSuite[] = [ ContentSuite, DeployToolsSuite, ToolsSuite, - ILEErrorSuite, SearchSuite, StorageSuite, EncodingSuite From 1c2bbc63e5c691dc0d8284b20a6e768470a2a45c Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 17 Jan 2025 10:16:41 -0500 Subject: [PATCH 20/28] Move search tests Signed-off-by: worksofliam --- src/api/tests/globalSetup.ts | 16 ++++- src/api/tests/suites/components.test.ts | 88 +++++++++++++------------ src/api/tests/suites/debug.test.ts | 2 +- src/api/tests/suites/search.test.ts | 47 +++++++++++++ src/testing/index.ts | 2 - src/testing/search.ts | 63 ------------------ 6 files changed, 108 insertions(+), 110 deletions(-) create mode 100644 src/api/tests/suites/search.test.ts delete mode 100644 src/testing/search.ts diff --git a/src/api/tests/globalSetup.ts b/src/api/tests/globalSetup.ts index 3801d5a58..a76a7f996 100644 --- a/src/api/tests/globalSetup.ts +++ b/src/api/tests/globalSetup.ts @@ -17,7 +17,7 @@ import { JsonConfig, JsonStorage } from "./testConfigSetup"; const testConfig = new JsonConfig(); const testStorage = new JsonStorage(); -beforeAll(async () => { +export async function newConnection() { const virtualStorage = testStorage; IBMi.GlobalStorage = new CodeForIStorage(virtualStorage); @@ -68,6 +68,20 @@ beforeAll(async () => { expect(result).toBeDefined(); expect(result.success).toBeTruthy(); + return conn; +} + +beforeAll(async () => { + const virtualStorage = testStorage; + + IBMi.GlobalStorage = new CodeForIStorage(virtualStorage); + IBMi.connectionManager.configMethod = testConfig; + + await testStorage.load(); + await testConfig.load(); + + const conn = await newConnection(); + setConnection(conn); }, 10000000); diff --git a/src/api/tests/suites/components.test.ts b/src/api/tests/suites/components.test.ts index 6c593263f..9e3246422 100644 --- a/src/api/tests/suites/components.test.ts +++ b/src/api/tests/suites/components.test.ts @@ -4,46 +4,48 @@ import { GetNewLibl } from '../../components/getNewLibl'; import { Tools } from '../../Tools'; import { getConnection } from '../state'; - describe('Component Tests', () => { - it('Get new libl', async () => { - const connection = getConnection(); - const component = connection.getComponent(GetNewLibl.ID); - - if (component) { - const newLibl = await component.getLibraryListFromCommand(connection, `CHGLIBL CURLIB(SYSTOOLS)`); - expect(newLibl?.currentLibrary).toBe(`SYSTOOLS`); - } else { - throw new Error(`Component not installed`); - } - }); - - it('Check getMemberInfo', async () => { - const connection = getConnection(); - const component = connection?.getComponent(GetMemberInfo.ID)!; - - expect(component).toBeTruthy(); - - const memberInfoA = await component.getMemberInfo(connection, `QSYSINC`, `H`, `MATH`); - expect(memberInfoA).toBeTruthy(); - expect(memberInfoA?.library).toBe(`QSYSINC`); - expect(memberInfoA?.file).toBe(`H`); - expect(memberInfoA?.name).toBe(`MATH`); - expect(memberInfoA?.extension).toBe(`C`); - expect(memberInfoA?.text).toBe(`STANDARD HEADER FILE MATH`); - - const memberInfoB = await component.getMemberInfo(connection, `QSYSINC`, `H`, `MEMORY`); - expect(memberInfoB).toBeTruthy(); - expect(memberInfoB?.library).toBe(`QSYSINC`); - expect(memberInfoB?.file).toBe(`H`); - expect(memberInfoB?.name).toBe(`MEMORY`); - expect(memberInfoB?.extension).toBe(`CPP`); - expect(memberInfoB?.text).toBe(`C++ HEADER`); - - try { - await component.getMemberInfo(connection, `QSYSINC`, `H`, `OH_NONO`); - } catch (error: any) { - expect(error).toBeInstanceOf(Tools.SqlError); - expect(error.sqlstate).toBe("38501"); - } - }); - }); +describe('Component Tests', () => { + + + it('Get new libl', async () => { + const connection = getConnection(); + const component = connection.getComponent(GetNewLibl.ID); + + if (component) { + const newLibl = await component.getLibraryListFromCommand(connection, `CHGLIBL CURLIB(SYSTOOLS)`); + expect(newLibl?.currentLibrary).toBe(`SYSTOOLS`); + } else { + throw new Error(`Component not installed`); + } + }); + + it('Check getMemberInfo', async () => { + const connection = getConnection(); + const component = connection?.getComponent(GetMemberInfo.ID)!; + + expect(component).toBeTruthy(); + + const memberInfoA = await component.getMemberInfo(connection, `QSYSINC`, `H`, `MATH`); + expect(memberInfoA).toBeTruthy(); + expect(memberInfoA?.library).toBe(`QSYSINC`); + expect(memberInfoA?.file).toBe(`H`); + expect(memberInfoA?.name).toBe(`MATH`); + expect(memberInfoA?.extension).toBe(`C`); + expect(memberInfoA?.text).toBe(`STANDARD HEADER FILE MATH`); + + const memberInfoB = await component.getMemberInfo(connection, `QSYSINC`, `H`, `MEMORY`); + expect(memberInfoB).toBeTruthy(); + expect(memberInfoB?.library).toBe(`QSYSINC`); + expect(memberInfoB?.file).toBe(`H`); + expect(memberInfoB?.name).toBe(`MEMORY`); + expect(memberInfoB?.extension).toBe(`CPP`); + expect(memberInfoB?.text).toBe(`C++ HEADER`); + + try { + await component.getMemberInfo(connection, `QSYSINC`, `H`, `OH_NONO`); + } catch (error: any) { + expect(error).toBeInstanceOf(Tools.SqlError); + expect(error.sqlstate).toBe("38501"); + } + }); +}); diff --git a/src/api/tests/suites/debug.test.ts b/src/api/tests/suites/debug.test.ts index d5bb528cc..9934de5dc 100644 --- a/src/api/tests/suites/debug.test.ts +++ b/src/api/tests/suites/debug.test.ts @@ -5,7 +5,7 @@ import { getJavaHome } from '../../configuration/DebugConfiguration'; describe('Debug engine tests', () => { it('Check Java versions', async () => { const connection = getConnection(); - + if (connection.remoteFeatures.jdk80) { const jdk8 = getJavaHome(connection, '8'); expect(jdk8).toBe(connection.remoteFeatures.jdk80); diff --git a/src/api/tests/suites/search.test.ts b/src/api/tests/suites/search.test.ts new file mode 100644 index 000000000..7dbdc337f --- /dev/null +++ b/src/api/tests/suites/search.test.ts @@ -0,0 +1,47 @@ +import { describe, it, expect } from 'vitest'; +import { parseFilter } from '../../Filter'; +import { Search } from '../../Search'; +import { getConnection } from '../state'; + +describe('Search Tests', {concurrent: true}, () => { + it('Single member search', async () => { + const result = await Search.searchMembers(getConnection(), "QSYSINC", "QRPGLESRC", "IBM", "CMRPG"); + expect(result.term).toBe("IBM"); + expect(result.hits.length).toBe(1); + const [hit] = result.hits; + expect(hit.lines.length).toBe(3); + + const checkLine = (index: number, expectedNumber: number) => { + expect(hit.lines[index].number).toBe(expectedNumber); + expect(hit.lines[index].content).toContain(result.term); + } + + checkLine(0, 7); + checkLine(1, 11); + checkLine(2, 13); + }); + + it('Generic name search', async () => { + const memberFilter = "E*"; + const filter = parseFilter(memberFilter); + const result = await Search.searchMembers(getConnection(), "QSYSINC", "QRPGLESRC", "IBM", memberFilter); + expect(result.hits.every(hit => filter.test(hit.path.split("/").at(-1)!))).toBe(true); + expect(result.hits.every(hit => !hit.path.endsWith(`MBR`))).toBe(true); + }); + + it('Filtered members list search', async () => { + const library = "QSYSINC"; + const sourceFile = "QRPGLESRC"; + const memberFilter = "S*,T*"; + const filter = parseFilter(memberFilter); + const checkNames = (names: string[]) => names.every(filter.test); + + const members = await getConnection().getContent().getMemberList({ library, sourceFile, members: memberFilter }); + expect(checkNames(members.map(member => member.name))).toBe(true); + + const result = await Search.searchMembers(getConnection(), "QSYSINC", "QRPGLESRC", "SQL", members); + expect(result.hits.length).toBe(6); + expect(checkNames(result.hits.map(hit => hit.path.split("/").at(-1)!))).toBe(true); + expect(result.hits.every(hit => !hit.path.endsWith(`MBR`))).toBe(true); + }); +}); diff --git a/src/testing/index.ts b/src/testing/index.ts index 37faf5142..97a9438ff 100644 --- a/src/testing/index.ts +++ b/src/testing/index.ts @@ -5,7 +5,6 @@ import { ActionSuite } from "./action"; import { ContentSuite } from "./content"; import { DeployToolsSuite } from "./deployTools"; import { EncodingSuite } from "./encoding"; -import { SearchSuite } from "./search"; import { StorageSuite } from "./storage"; import { TestSuitesTreeProvider } from "./testCasesTree"; import { ToolsSuite } from "./tools"; @@ -16,7 +15,6 @@ const suites: TestSuite[] = [ ContentSuite, DeployToolsSuite, ToolsSuite, - SearchSuite, StorageSuite, EncodingSuite ] diff --git a/src/testing/search.ts b/src/testing/search.ts deleted file mode 100644 index 4bf0f35d6..000000000 --- a/src/testing/search.ts +++ /dev/null @@ -1,63 +0,0 @@ -import assert from "assert"; -import { TestSuite } from "."; -import { parseFilter } from "../api/Filter"; -import { Search } from "../api/Search"; -import { instance } from "../instantiate"; - -export const SearchSuite: TestSuite = { - name: `Search API tests`, - tests: [ - { - name: "Single member search", test: async () => { - const result = await Search.searchMembers(instance.getConnection()!, "QSYSINC", "QRPGLESRC", "IBM", "CMRPG"); - assert.strictEqual(result.term, "IBM"); - assert.strictEqual(result.hits.length, 1); - const [hit] = result.hits; - assert.strictEqual(hit.lines.length, 3); - - const checkLine = (index: number, expectedNumber: number) => { - assert.strictEqual(hit.lines[index].number, expectedNumber); - assert.ok(hit.lines[index].content.includes(result.term)); - } - - checkLine(0, 7); - checkLine(1, 11); - checkLine(2, 13); - } - }, - { - name: "Generic name search", test: async () => { - const memberFilter = "E*"; - const filter = parseFilter(memberFilter); - const result = await Search.searchMembers(instance.getConnection()!, "QSYSINC", "QRPGLESRC", "IBM", memberFilter); - assert.ok(result.hits.every(hit => filter.test(hit.path.split("/").at(-1)!))); - assert.ok(result.hits.every(hit => !hit.path.endsWith(`MBR`))); - } - }, - { - name: "Filtered members list search", test: async () => { - const library = "QSYSINC"; - const sourceFile = "QRPGLESRC"; - const memberFilter = "S*,T*"; - const filter = parseFilter(memberFilter); - const checkNames = (names: string[]) => names.every(filter.test); - - const members = await getConnection().content.getMemberList({ library, sourceFile, members: memberFilter }); - assert.ok(checkNames(members.map(member => member.name))); - - const result = await Search.searchMembers(instance.getConnection()!, "QSYSINC", "QRPGLESRC", "SQL", members); - assert.strictEqual(result.hits.length, 6); - assert.ok(checkNames(result.hits.map(hit => hit.path.split("/").at(-1)!))); - assert.ok(result.hits.every(hit => !hit.path.endsWith(`MBR`))); - } - } - ] -} - -function getConnection() { - const connection = instance.getConnection(); - if (!connection) { - throw Error("Cannot run test: no connection") - } - return connection; -} \ No newline at end of file From 85b5e3bc11e2fc11773c8b6afc297462a0d4e333 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 17 Jan 2025 10:31:15 -0500 Subject: [PATCH 21/28] No more global setup for the connection since not all suites require a connection Signed-off-by: worksofliam --- src/api/IBMi.ts | 4 +- src/api/tests/globalSetup.ts | 33 ++-------- src/api/tests/state.ts | 11 ---- src/api/tests/suites/components.test.ts | 14 +++-- src/api/tests/suites/connection.test.ts | 75 +++++++++++------------ src/api/tests/suites/content.test.ts | 80 +++++++++++-------------- src/api/tests/suites/debug.test.ts | 26 -------- src/api/tests/suites/encoding.test.ts | 32 +++++----- src/api/tests/suites/search.test.ts | 22 +++++-- vitest.config.ts | 3 - 10 files changed, 122 insertions(+), 178 deletions(-) delete mode 100644 src/api/tests/state.ts delete mode 100644 src/api/tests/suites/debug.test.ts diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts index 13cce21c5..a30afc0db 100644 --- a/src/api/IBMi.ts +++ b/src/api/IBMi.ts @@ -1097,12 +1097,12 @@ export default class IBMi { */ getTempRemote(key: string) { if (this.tempRemoteFiles[key] !== undefined) { - console.log(`Using existing temp: ${this.tempRemoteFiles[key]}`); + // console.log(`Using existing temp: ${this.tempRemoteFiles[key]}`); return this.tempRemoteFiles[key]; } else if (this.config) { let value = path.posix.join(this.config.tempDir, `vscodetemp-${Tools.makeid()}`); - console.log(`Using new temp: ${value}`); + // console.log(`Using new temp: ${value}`); this.tempRemoteFiles[key] = value; return value; } diff --git a/src/api/tests/globalSetup.ts b/src/api/tests/globalSetup.ts index a76a7f996..1c5c6440d 100644 --- a/src/api/tests/globalSetup.ts +++ b/src/api/tests/globalSetup.ts @@ -1,11 +1,8 @@ import assert from "assert"; import IBMi from "../IBMi"; import { ENV_CREDS } from "./env"; -import { getConnection, setConnection } from "./state"; import { afterAll, beforeAll, expect } from "vitest"; import { CodeForIStorage } from "../configuration/storage/CodeForIStorage"; -import { VirtualConfig } from "../configuration/config/VirtualConfig"; -import { VirtualStorage } from "../configuration/storage/BaseStorage"; import { CustomQSh } from "../components/cqsh"; import path from "path"; import { CopyToImport } from "../components/copyToImport"; @@ -71,28 +68,8 @@ export async function newConnection() { return conn; } -beforeAll(async () => { - const virtualStorage = testStorage; - - IBMi.GlobalStorage = new CodeForIStorage(virtualStorage); - IBMi.connectionManager.configMethod = testConfig; - - await testStorage.load(); - await testConfig.load(); - - const conn = await newConnection(); - - setConnection(conn); -}, 10000000); - -afterAll(async () => { - const conn = getConnection(); - - if (conn) { - await conn.dispose(); - await testStorage.save(); - await testConfig.save(); - } else { - assert.fail(`Connection was not set`); - } -}) \ No newline at end of file +export function disposeConnection(conn: IBMi) { + conn.dispose(); + testStorage.save(); + testConfig.save(); +} \ No newline at end of file diff --git a/src/api/tests/state.ts b/src/api/tests/state.ts deleted file mode 100644 index 6ed5f0439..000000000 --- a/src/api/tests/state.ts +++ /dev/null @@ -1,11 +0,0 @@ -import IBMi from "../IBMi"; - -let connection: IBMi; - -export function setConnection(conn: IBMi) { - connection = conn; -} - -export function getConnection() { - return connection!; -} \ No newline at end of file diff --git a/src/api/tests/suites/components.test.ts b/src/api/tests/suites/components.test.ts index 9e3246422..7feac8ffb 100644 --- a/src/api/tests/suites/components.test.ts +++ b/src/api/tests/suites/components.test.ts @@ -1,14 +1,21 @@ -import { describe, it, expect } from 'vitest'; +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import { GetMemberInfo } from '../../components/getMemberInfo'; import { GetNewLibl } from '../../components/getNewLibl'; import { Tools } from '../../Tools'; -import { getConnection } from '../state'; +import IBMi from '../../IBMi'; +import { disposeConnection, newConnection } from '../globalSetup'; describe('Component Tests', () => { + let connection: IBMi + beforeAll(async () => { + connection = await newConnection(); + }) + afterAll(async () => { + disposeConnection(connection); + }); it('Get new libl', async () => { - const connection = getConnection(); const component = connection.getComponent(GetNewLibl.ID); if (component) { @@ -20,7 +27,6 @@ describe('Component Tests', () => { }); it('Check getMemberInfo', async () => { - const connection = getConnection(); const component = connection?.getComponent(GetMemberInfo.ID)!; expect(component).toBeTruthy(); diff --git a/src/api/tests/suites/connection.test.ts b/src/api/tests/suites/connection.test.ts index 6d4e5a9a2..9b7bfee90 100644 --- a/src/api/tests/suites/connection.test.ts +++ b/src/api/tests/suites/connection.test.ts @@ -1,12 +1,21 @@ -import { expect, test, describe } from 'vitest' -import { getConnection } from '../state' +import { expect, test, describe, afterAll, beforeAll, it } from 'vitest' import { Tools } from '../../Tools'; +import { disposeConnection, newConnection } from '../globalSetup'; +import IBMi from '../../IBMi'; +import { getJavaHome } from '../../configuration/DebugConfiguration'; describe(`connection tests`, {concurrent: true}, () => { - test('sendCommand', async () => { - const connection = getConnection(); + let connection: IBMi + beforeAll(async () => { + connection = await newConnection(); + }) + + afterAll(async () => { + disposeConnection(connection); + }); + test('sendCommand', async () => { const result = await connection.sendCommand({ command: `echo "Hello world"`, }); @@ -16,8 +25,6 @@ describe(`connection tests`, {concurrent: true}, () => { }) test('sendCommand with home directory', async () => { - const connection = getConnection(); - const resultA = await connection.sendCommand({ command: `pwd`, directory: `/QSYS.LIB` @@ -44,8 +51,6 @@ describe(`connection tests`, {concurrent: true}, () => { }); test('sendCommand with environment variables', async () => { - const connection = getConnection(); - const result = await connection.sendCommand({ command: `echo "$vara $varB $VARC"`, env: { @@ -60,8 +65,6 @@ describe(`connection tests`, {concurrent: true}, () => { }); test('getTempRemote', () => { - const connection = getConnection(); - const fileA = connection.getTempRemote(`/some/file`); const fileB = connection.getTempRemote(`/some/badfile`); const fileC = connection.getTempRemote(`/some/file`); @@ -71,8 +74,6 @@ describe(`connection tests`, {concurrent: true}, () => { }) test('parseMemberPath (simple)', () => { - const connection = getConnection(); - const memberA = connection.parserMemberPath(`/thelib/thespf/thembr.mbr`); expect(memberA?.asp).toBeUndefined(); @@ -84,8 +85,6 @@ describe(`connection tests`, {concurrent: true}, () => { }) test('parseMemberPath (ASP)', () => { - const connection = getConnection(); - const memberA = connection.parserMemberPath(`/theasp/thelib/thespf/thembr.mbr`); expect(memberA?.asp).toBe(`THEASP`); @@ -97,8 +96,6 @@ describe(`connection tests`, {concurrent: true}, () => { }) test('parseMemberPath (no root)', () => { - const connection = getConnection(); - const memberA = connection.parserMemberPath(`thelib/thespf/thembr.mbr`); expect(memberA?.asp).toBe(undefined); @@ -110,8 +107,6 @@ describe(`connection tests`, {concurrent: true}, () => { }); test('parseMemberPath (no extension)', () => { - const connection = getConnection(); - const memberA = connection.parserMemberPath(`/thelib/thespf/thembr`); expect(memberA?.asp).toBe(undefined); @@ -127,16 +122,12 @@ describe(`connection tests`, {concurrent: true}, () => { }); test('parseMemberPath (invalid length)', () => { - const connection = getConnection(); - expect( () => { connection.parserMemberPath(`/thespf/thembr.mbr`) } ).toThrow(`Invalid path: /thespf/thembr.mbr. Use format LIB/SPF/NAME.ext`); }); test('runCommand (ILE)', async () => { - const connection = getConnection(); - const result = await connection.runCommand({ command: `DSPJOB OPTION(*DFNA)`, environment: `ile` @@ -147,7 +138,7 @@ describe(`connection tests`, {concurrent: true}, () => { }) test('runCommand (ILE, with error)', async () => { - const result = await getConnection().runCommand({ + const result = await connection.runCommand({ command: `CHKOBJ OBJ(QSYS/NOEXIST) OBJTYPE(*DTAARA)`, noLibList: true }); @@ -156,9 +147,7 @@ describe(`connection tests`, {concurrent: true}, () => { expect(result?.stderr).toBeTruthy(); }); - test('runCommand (ILE, custom library list)', async () => { - const connection = getConnection(); - const config = connection.getConfig(); + test('runCommand (ILE, custom library list)', async () => { const config = connection.getConfig(); const ogLibl = config!.libraryList.slice(0); @@ -190,8 +179,6 @@ describe(`connection tests`, {concurrent: true}, () => { }); test('runCommand (ILE, library list order from variable)', async () => { - const connection = getConnection(); - const result = await connection?.runCommand({ command: `DSPLIBL`, environment: `ile`, @@ -209,9 +196,7 @@ describe(`connection tests`, {concurrent: true}, () => { expect(qtempIndex < qsysincIndex).toBeTruthy(); }); - test('runCommand (ILE, library order from config)', async () => { - const connection = getConnection(); - const config = connection.getConfig(); + test('runCommand (ILE, library order from config)', async () => { const config = connection.getConfig(); const ogLibl = config!.libraryList.slice(0); @@ -233,9 +218,7 @@ describe(`connection tests`, {concurrent: true}, () => { expect(qtempIndex < qsysincIndex).toBeTruthy(); }); - test('withTempDirectory and countFiles', async () => { - const connection = getConnection(); - const content = connection.getContent()!; + test('withTempDirectory and countFiles', async () => { const content = connection.getContent()!; let temp; await connection.withTempDirectory(async tempDir => { @@ -265,8 +248,7 @@ describe(`connection tests`, {concurrent: true}, () => { test('upperCaseName', () => { { - const connection = getConnection(); - const variantsBackup = connection.variantChars.local; + const variantsBackup = connection.variantChars.local; try { //CCSID 297 variants @@ -285,5 +267,24 @@ describe(`connection tests`, {concurrent: true}, () => { connection.variantChars.local = variantsBackup; } } - }) + }); + + it('Check Java versions', async () => { + if (connection.remoteFeatures.jdk80) { + const jdk8 = getJavaHome(connection, '8'); + expect(jdk8).toBe(connection.remoteFeatures.jdk80); + } + + if (connection.remoteFeatures.jdk11) { + const jdk11 = getJavaHome(connection, '11'); + expect(jdk11).toBe(connection.remoteFeatures.jdk11); + } + + if (connection.remoteFeatures.jdk17) { + const jdk17 = getJavaHome(connection, '17'); + expect(jdk17).toBe(connection.remoteFeatures.jdk17); + } + + expect(getJavaHome(connection, '666')).toBeUndefined(); + }); }) \ No newline at end of file diff --git a/src/api/tests/suites/content.test.ts b/src/api/tests/suites/content.test.ts index 8c02bce01..587ab46fd 100644 --- a/src/api/tests/suites/content.test.ts +++ b/src/api/tests/suites/content.test.ts @@ -1,14 +1,23 @@ -import { expect, describe, it } from 'vitest'; -import { getConnection } from '../state'; +import { expect, describe, it, afterAll, beforeAll } from 'vitest'; import util, { TextDecoder } from 'util'; import tmp from 'tmp'; import { Tools } from '../../Tools'; import { posix } from 'path'; +import IBMi from '../../IBMi'; +import { newConnection, disposeConnection } from '../globalSetup'; describe('Content Tests', {concurrent: true}, () => { + let connection: IBMi + beforeAll(async () => { + connection = await newConnection(); + }) + + afterAll(async () => { + disposeConnection(connection); + }); + it('memberResolve', async () => { - const connection = getConnection(); const content = connection.getContent(); const member = await content?.memberResolve('MATH', [ @@ -27,7 +36,6 @@ describe('Content Tests', {concurrent: true}, () => { }); it('memberResolve (with invalid ASP)', async () => { - const connection = getConnection(); const content = connection.getContent(); const member = await content?.memberResolve('MATH', [ @@ -46,7 +54,6 @@ describe('Content Tests', {concurrent: true}, () => { }); it('memberResolve with variants', async () => { - const connection = getConnection(); const content = connection.getContent(); const config = connection.getConfig(); const tempLib = config!.tempLibrary, @@ -81,7 +88,6 @@ describe('Content Tests', {concurrent: true}, () => { }); it('memberResolve with bad name', async () => { - const connection = getConnection(); const content = connection.getContent(); const member = await content?.memberResolve('BOOOP', [ @@ -94,7 +100,6 @@ describe('Content Tests', {concurrent: true}, () => { }); it('objectResolve .FILE', async () => { - const connection = getConnection(); const content = connection.getContent(); const lib = await content?.objectResolve('MIH', [ @@ -106,7 +111,6 @@ describe('Content Tests', {concurrent: true}, () => { }); it('objectResolve .PGM', async () => { - const connection = getConnection(); const content = connection.getContent(); const lib = await content?.objectResolve('CMRCV', [ @@ -118,7 +122,6 @@ describe('Content Tests', {concurrent: true}, () => { }); it('objectResolve .DTAARA with variants', async () => { - const connection = getConnection(); const content = connection.getContent(); const config = connection.getConfig(); const tempLib = config!.tempLibrary, @@ -145,7 +148,6 @@ describe('Content Tests', {concurrent: true}, () => { }); it('objectResolve with bad name', async () => { - const connection = getConnection(); const content = connection.getContent(); const lib = await content?.objectResolve('BOOOP', [ @@ -158,7 +160,7 @@ describe('Content Tests', {concurrent: true}, () => { }); it('streamfileResolve', async () => { - const content = getConnection().getContent(); + const content = connection.getContent(); const streamfilePath = await content?.streamfileResolve(['git'], ['/QOpenSys/pkgs/sbin', '/QOpenSys/pkgs/bin']); @@ -166,7 +168,7 @@ describe('Content Tests', {concurrent: true}, () => { }); it('streamfileResolve with bad name', async () => { - const content = getConnection().getContent(); + const content = connection.getContent(); const streamfilePath = await content?.streamfileResolve(['sup'], ['/QOpenSys/pkgs/sbin', '/QOpenSys/pkgs/bin']); @@ -174,7 +176,7 @@ describe('Content Tests', {concurrent: true}, () => { }); it('streamfileResolve with multiple names', async () => { - const content = getConnection().getContent(); + const content = connection.getContent(); const streamfilePath = await content?.streamfileResolve(['sup', 'sup2', 'git'], ['/QOpenSys/pkgs/sbin', '/QOpenSys/pkgs/bin']); @@ -182,7 +184,6 @@ describe('Content Tests', {concurrent: true}, () => { }); it('streamfileResolve with blanks in names', async () => { - const connection = getConnection(); const content = connection.getContent(); const files = ['normalname', 'name with blank', 'name_with_quote\'', 'name_with_dollar$']; const dir = `/tmp/${Date.now()}`; @@ -210,7 +211,7 @@ describe('Content Tests', {concurrent: true}, () => { }); it('Test downloadMemberContent', async () => { - const content = getConnection().getContent(); + const content = connection.getContent(); const tmpFile = await util.promisify(tmp.file)(); const memberContent = await content?.downloadMemberContent(undefined, 'QSYSINC', 'H', 'MATH', tmpFile); @@ -219,7 +220,6 @@ describe('Content Tests', {concurrent: true}, () => { }); it('Test runSQL (basic select)', async () => { - const connection = getConnection(); const rows = await connection.runSQL('select * from qiws.qcustcdt'); expect(rows?.length).not.toBe(0); @@ -230,7 +230,6 @@ describe('Content Tests', {concurrent: true}, () => { }); it('Test runSQL (bad basic select)', async () => { - const connection = getConnection(); try { await connection.runSQL('select from qiws.qcustcdt'); @@ -242,7 +241,6 @@ describe('Content Tests', {concurrent: true}, () => { }); it('Test runSQL (with comments)', async () => { - const connection = getConnection(); const rows = await connection.runSQL([ '-- myselect', @@ -255,7 +253,6 @@ describe('Content Tests', {concurrent: true}, () => { }); it('Test getTable', async () => { - const connection = getConnection(); const content = connection.getContent(); const rows = await content.getTable('qiws', 'qcustcdt', '*all'); @@ -268,7 +265,7 @@ describe('Content Tests', {concurrent: true}, () => { }); it('Test validateLibraryList', async () => { - const content = getConnection().getContent(); + const content = connection.getContent(); const badLibs = await content.validateLibraryList(['SCOOBY', 'QSYSINC', 'BEEPBOOP']); @@ -278,7 +275,7 @@ describe('Content Tests', {concurrent: true}, () => { }); it('Test getFileList', async () => { - const content = getConnection().getContent(); + const content = connection.getContent(); const objects = await content?.getFileList('/'); @@ -291,7 +288,7 @@ describe('Content Tests', {concurrent: true}, () => { }); it('Test getFileList (non-existing file)', async () => { - const content = getConnection().getContent(); + const content = connection.getContent(); const objects = await content?.getFileList(`/tmp/${Date.now()}`); @@ -299,8 +296,7 @@ describe('Content Tests', {concurrent: true}, () => { }); it('Test getFileList (special chars)', async () => { - const connection = getConnection(); - const content = getConnection().getContent(); + const content = connection.getContent(); const files = ['name with blank', 'name_with_quote\'', 'name_with_dollar$']; const dir = `/tmp/${Date.now()}`; const dirWithSubdir = `${dir}/${files[0]}`; @@ -326,7 +322,7 @@ describe('Content Tests', {concurrent: true}, () => { }); it('Test getObjectList (all objects)', async () => { - const content = getConnection().getContent(); + const content = connection.getContent(); const objects = await content?.getObjectList({ library: 'QSYSINC' }); @@ -334,7 +330,7 @@ describe('Content Tests', {concurrent: true}, () => { }); it('Test getObjectList (pgm filter)', async () => { - const content = getConnection().getContent(); + const content = connection.getContent(); const objects = await content?.getObjectList({ library: 'QSYSINC', types: ['*PGM'] }); @@ -346,7 +342,7 @@ describe('Content Tests', {concurrent: true}, () => { }); it('Test getObjectList (source files only)', async () => { - const content = getConnection().getContent(); + const content = connection.getContent(); const objects = await content?.getObjectList({ library: 'QSYSINC', types: ['*SRCPF'] }); @@ -358,7 +354,7 @@ describe('Content Tests', {concurrent: true}, () => { }); it('Test getObjectList (single source file only, detailed)', async () => { - const content = getConnection().getContent(); + const content = connection.getContent(); const objectsA = await content?.getObjectList({ library: 'QSYSINC', types: ['*SRCPF'], object: 'MIH' }); @@ -366,7 +362,7 @@ describe('Content Tests', {concurrent: true}, () => { }); it('Test getObjectList (source files only, named filter)', async () => { - const content = getConnection().getContent(); + const content = connection.getContent(); const objects = await content?.getObjectList({ library: 'QSYSINC', types: ['*SRCPF'], object: 'MIH' }); @@ -377,7 +373,7 @@ describe('Content Tests', {concurrent: true}, () => { }); it('getLibraries (simple filters)', async () => { - const content = getConnection().getContent(); + const content = connection.getContent(); const qsysLibraries = await content?.getLibraries({ library: 'QSYS*' }); expect(qsysLibraries?.length).not.toBe(0); @@ -394,7 +390,7 @@ describe('Content Tests', {concurrent: true}, () => { }); it('getLibraries (regexp filters)', async () => { - const content = getConnection().getContent(); + const content = connection.getContent(); const qsysLibraries = await content?.getLibraries({ library: '^.*SYS[^0-9]*$', filterType: 'regex' }); expect(qsysLibraries?.length).not.toBe(0); @@ -402,7 +398,7 @@ describe('Content Tests', {concurrent: true}, () => { }); it('getObjectList (advanced filtering)', async () => { - const content = getConnection().getContent(); + const content = connection.getContent(); const objects = await content?.getObjectList({ library: 'QSYSINC', object: 'L*OU*' }); expect(objects?.length).not.toBe(0); @@ -410,7 +406,7 @@ describe('Content Tests', {concurrent: true}, () => { }); it('getMemberList (SQL, no filter)', async () => { - const content = getConnection().getContent(); + const content = connection.getContent(); let members = await content?.getMemberList({ library: 'qsysinc', sourceFile: 'mih', members: '*inxen' }); @@ -428,7 +424,7 @@ describe('Content Tests', {concurrent: true}, () => { }); it('getMemberList (advanced filtering)', async () => { - const content = getConnection().getContent(); + const content = connection.getContent(); const members = await content?.getMemberList({ library: 'QSYSINC', sourceFile: 'QRPGLESRC', members: 'SYS*,I*,*EX' }); expect(members?.length).not.toBe(0); @@ -440,7 +436,7 @@ describe('Content Tests', {concurrent: true}, () => { }); it('getQtempTable', async () => { - const content = getConnection().getContent(); + const content = connection.getContent(); const queries = [ `CALL QSYS2.QCMDEXC('DSPOBJD OBJ(QSYSINC/*ALL) OBJTYPE(*ALL) OUTPUT(*OUTFILE) OUTFILE(QTEMP/DSPOBJD)')`, @@ -473,7 +469,7 @@ describe('Content Tests', {concurrent: true}, () => { }); it('toCl', () => { - const command = getConnection().getContent().toCl("TEST", { + const command = connection.getContent().toCl("TEST", { ZERO: 0, NONE: '*NONE', EMPTY: `''`, @@ -486,21 +482,21 @@ describe('Content Tests', {concurrent: true}, () => { }); it('Check object (no exist)', async () => { - const content = getConnection().getContent(); + const content = connection.getContent(); const exists = await content?.checkObject({ library: 'QSYSINC', name: 'BOOOP', type: '*FILE' }); expect(exists).toBe(false); }); it('Check object (source member)', async () => { - const content = getConnection().getContent(); + const content = connection.getContent(); const exists = await content?.checkObject({ library: 'QSYSINC', name: 'H', type: '*FILE', member: 'MATH' }); expect(exists).toBeTruthy(); }); it('Check getMemberInfo', async () => { - const content = getConnection().getContent(); + const content = connection.getContent(); const memberInfoA = await content?.getMemberInfo('QSYSINC', 'H', 'MATH'); expect(memberInfoA).toBeTruthy(); @@ -527,7 +523,7 @@ describe('Content Tests', {concurrent: true}, () => { }); it('Test @clCommand + select statement', async () => { - const content = getConnection().getContent(); + const content = connection.getContent(); const [resultA] = await content.runSQL(`@CRTSAVF FILE(QTEMP/UNITTEST) TEXT('Code for i test');\nSelect * From Table(QSYS2.OBJECT_STATISTICS('QTEMP', '*FILE')) Where OBJATTRIBUTE = 'SAVF';`); @@ -544,7 +540,6 @@ describe('Content Tests', {concurrent: true}, () => { }); it('should get attributes', async () => { - const connection = getConnection(); const content = connection.getContent() await connection.withTempDirectory(async directory => { expect((await connection.sendCommand({ command: 'echo "I am a test file" > test.txt', directory })).code).toBe(0); @@ -576,7 +571,6 @@ describe('Content Tests', {concurrent: true}, () => { }); it('should count members', async () => { - const connection = getConnection(); const content = connection.getContent() const tempLib = connection.config?.tempLibrary; if (tempLib) { @@ -608,7 +602,6 @@ describe('Content Tests', {concurrent: true}, () => { }); it('should create streamfile', async () => { - const connection = getConnection(); const content = connection.getContent() await connection.withTempDirectory(async dir => { const file = posix.join(dir, Tools.makeid()); @@ -623,7 +616,6 @@ describe('Content Tests', {concurrent: true}, () => { }); it('should handle long library name', async () => { - const connection = getConnection(); const content = connection.getContent() const longName = Tools.makeid(18); const shortName = Tools.makeid(8); diff --git a/src/api/tests/suites/debug.test.ts b/src/api/tests/suites/debug.test.ts deleted file mode 100644 index 9934de5dc..000000000 --- a/src/api/tests/suites/debug.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { getConnection } from '../state'; -import { getJavaHome } from '../../configuration/DebugConfiguration'; - -describe('Debug engine tests', () => { - it('Check Java versions', async () => { - const connection = getConnection(); - - if (connection.remoteFeatures.jdk80) { - const jdk8 = getJavaHome(connection, '8'); - expect(jdk8).toBe(connection.remoteFeatures.jdk80); - } - - if (connection.remoteFeatures.jdk11) { - const jdk11 = getJavaHome(connection, '11'); - expect(jdk11).toBe(connection.remoteFeatures.jdk11); - } - - if (connection.remoteFeatures.jdk17) { - const jdk17 = getJavaHome(connection, '17'); - expect(jdk17).toBe(connection.remoteFeatures.jdk17); - } - - expect(getJavaHome(connection, '666')).toBeUndefined(); - }); -}); diff --git a/src/api/tests/suites/encoding.test.ts b/src/api/tests/suites/encoding.test.ts index e20c1df9c..8b8b7745e 100644 --- a/src/api/tests/suites/encoding.test.ts +++ b/src/api/tests/suites/encoding.test.ts @@ -2,8 +2,8 @@ import path from "path"; import IBMi from "../../IBMi"; import { Tools } from "../../Tools"; import { IBMiObject } from "../../types"; -import { describe, it, expect } from 'vitest'; -import { getConnection } from "../state"; +import { describe, it, expect, afterAll, beforeAll } from 'vitest'; +import { newConnection, disposeConnection } from "../globalSetup"; const contents = { '37': [`Hello world`], @@ -39,9 +39,17 @@ async function runCommandsWithCCSID(connection: IBMi, commands: string[], ccsid: return result; } -describe('Encoding tests', () => { - it('Prove that input strings are messed up by CCSID', async () => { - const connection = getConnection(); +describe('Encoding tests', {concurrent: true} ,() => { + let connection: IBMi + beforeAll(async () => { + connection = await newConnection(); + }) + + afterAll(async () => { + disposeConnection(connection); + }); + + it('Prove that input strings are messed up by CCSID', {timeout: 40000}, async () => { let howManyTimesItMessedUpTheResult = 0; for (const strCcsid in contents) { @@ -62,10 +70,9 @@ describe('Encoding tests', () => { } expect(howManyTimesItMessedUpTheResult).toBeTruthy(); - }, {timeout: 40000}); + }); it('Compare Unicode to EBCDIC successfully', async () => { - const connection = getConnection(); const sql = `select table_name, table_owner from qsys2.systables where table_schema = ? and table_name = ?`; const result = await connection?.runSQL(sql, { fakeBindings: [`QSYS2`, `SYSCOLUMNS`] }); @@ -73,7 +80,6 @@ describe('Encoding tests', () => { }); it('Run variants through shells', async () => { - const connection = getConnection(); const text = `Hello${connection?.variantChars.local}world`; const basicCommandA = `echo "${IBMi.escapeForShell(text)}"`; @@ -107,8 +113,6 @@ describe('Encoding tests', () => { }); it('streamfileResolve with dollar', async () => { - const connection = getConnection()!; - await connection.withTempDirectory(async tempDir => { const tempFile = path.posix.join(tempDir, `$hello`); await connection.content.createStreamFile(tempFile); @@ -121,8 +125,6 @@ describe('Encoding tests', () => { SHELL_CHARS.forEach(char => { it(`Test streamfiles with shell character ${char}`, async () => { - const connection = getConnection()!; - const nameCombos = [`${char}ABC`, `ABC${char}`, `${char}ABC${char}`, `A${char}C`]; await connection.withTempDirectory(async tempDir => { @@ -140,7 +142,6 @@ describe('Encoding tests', () => { }); it(`Test members with shell character ${char}`, async () => { - const connection = getConnection(); const content = connection.getContent(); const config = connection.getConfig() @@ -176,8 +177,7 @@ describe('Encoding tests', () => { }); it('Listing objects with variants', async () => { - const connection = getConnection(); - const content = getConnection()?.content; + const content = connection.getContent(); if (connection && content) { const tempLib = connection.config?.tempLibrary!; const ccsid = connection.getCcsid(); @@ -259,7 +259,6 @@ describe('Encoding tests', () => { }); it('Library list supports dollar sign variant', async () => { - const connection = getConnection()!; const library = `TEST${connection.variantChars.local}LIB`; const sourceFile = `TEST${connection.variantChars.local}FIL`; const member = `TEST${connection.variantChars.local}MBR`; @@ -290,7 +289,6 @@ describe('Encoding tests', () => { }); it('Variant character in source names and commands', async () => { - const connection = getConnection(); const config = connection.getConfig(); const ccsidData = connection.getCcsids()!; diff --git a/src/api/tests/suites/search.test.ts b/src/api/tests/suites/search.test.ts index 7dbdc337f..2cd829c0d 100644 --- a/src/api/tests/suites/search.test.ts +++ b/src/api/tests/suites/search.test.ts @@ -1,11 +1,21 @@ -import { describe, it, expect } from 'vitest'; +import { describe, it, expect, afterAll, beforeAll } from 'vitest'; import { parseFilter } from '../../Filter'; import { Search } from '../../Search'; -import { getConnection } from '../state'; +import IBMi from '../../IBMi'; +import { newConnection, disposeConnection } from '../globalSetup'; describe('Search Tests', {concurrent: true}, () => { + let connection: IBMi + beforeAll(async () => { + connection = await newConnection(); + }) + + afterAll(async () => { + disposeConnection(connection); + }); + it('Single member search', async () => { - const result = await Search.searchMembers(getConnection(), "QSYSINC", "QRPGLESRC", "IBM", "CMRPG"); + const result = await Search.searchMembers(connection, "QSYSINC", "QRPGLESRC", "IBM", "CMRPG"); expect(result.term).toBe("IBM"); expect(result.hits.length).toBe(1); const [hit] = result.hits; @@ -24,7 +34,7 @@ describe('Search Tests', {concurrent: true}, () => { it('Generic name search', async () => { const memberFilter = "E*"; const filter = parseFilter(memberFilter); - const result = await Search.searchMembers(getConnection(), "QSYSINC", "QRPGLESRC", "IBM", memberFilter); + const result = await Search.searchMembers(connection, "QSYSINC", "QRPGLESRC", "IBM", memberFilter); expect(result.hits.every(hit => filter.test(hit.path.split("/").at(-1)!))).toBe(true); expect(result.hits.every(hit => !hit.path.endsWith(`MBR`))).toBe(true); }); @@ -36,10 +46,10 @@ describe('Search Tests', {concurrent: true}, () => { const filter = parseFilter(memberFilter); const checkNames = (names: string[]) => names.every(filter.test); - const members = await getConnection().getContent().getMemberList({ library, sourceFile, members: memberFilter }); + const members = await connection.getContent().getMemberList({ library, sourceFile, members: memberFilter }); expect(checkNames(members.map(member => member.name))).toBe(true); - const result = await Search.searchMembers(getConnection(), "QSYSINC", "QRPGLESRC", "SQL", members); + const result = await Search.searchMembers(connection, "QSYSINC", "QRPGLESRC", "SQL", members); expect(result.hits.length).toBe(6); expect(checkNames(result.hits.map(hit => hit.path.split("/").at(-1)!))).toBe(true); expect(result.hits.every(hit => !hit.path.endsWith(`MBR`))).toBe(true); diff --git a/vitest.config.ts b/vitest.config.ts index fe0530057..ff1871fb8 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -6,9 +6,6 @@ export default defineConfig({ // ... Specify options here. fileParallelism: false, root: './src/api', - setupFiles: [ - 'tests/globalSetup.ts', - ], testTimeout: 10000 }, }) \ No newline at end of file From 092c5db0881f801acb94d918ba79f0f73eaf87f6 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 17 Jan 2025 10:32:15 -0500 Subject: [PATCH 22/28] Use it over test Signed-off-by: worksofliam --- src/api/tests/suites/connection.test.ts | 34 ++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/api/tests/suites/connection.test.ts b/src/api/tests/suites/connection.test.ts index 9b7bfee90..3f0c749c5 100644 --- a/src/api/tests/suites/connection.test.ts +++ b/src/api/tests/suites/connection.test.ts @@ -1,5 +1,5 @@ -import { expect, test, describe, afterAll, beforeAll, it } from 'vitest' +import { expect, describe, afterAll, beforeAll, it } from 'vitest' import { Tools } from '../../Tools'; import { disposeConnection, newConnection } from '../globalSetup'; import IBMi from '../../IBMi'; @@ -15,7 +15,7 @@ describe(`connection tests`, {concurrent: true}, () => { disposeConnection(connection); }); - test('sendCommand', async () => { + it('sendCommand', async () => { const result = await connection.sendCommand({ command: `echo "Hello world"`, }); @@ -24,7 +24,7 @@ describe(`connection tests`, {concurrent: true}, () => { expect(result.stdout).toBe('Hello world'); }) - test('sendCommand with home directory', async () => { + it('sendCommand with home directory', async () => { const resultA = await connection.sendCommand({ command: `pwd`, directory: `/QSYS.LIB` @@ -50,7 +50,7 @@ describe(`connection tests`, {concurrent: true}, () => { expect(resultC.stdout).not.toBe('/badnaughty'); }); - test('sendCommand with environment variables', async () => { + it('sendCommand with environment variables', async () => { const result = await connection.sendCommand({ command: `echo "$vara $varB $VARC"`, env: { @@ -64,7 +64,7 @@ describe(`connection tests`, {concurrent: true}, () => { expect(result.stdout).toBe('Hello world cool'); }); - test('getTempRemote', () => { + it('getTempRemote', () => { const fileA = connection.getTempRemote(`/some/file`); const fileB = connection.getTempRemote(`/some/badfile`); const fileC = connection.getTempRemote(`/some/file`); @@ -73,7 +73,7 @@ describe(`connection tests`, {concurrent: true}, () => { expect(fileA).not.toBe(fileB); }) - test('parseMemberPath (simple)', () => { + it('parseMemberPath (simple)', () => { const memberA = connection.parserMemberPath(`/thelib/thespf/thembr.mbr`); expect(memberA?.asp).toBeUndefined(); @@ -84,7 +84,7 @@ describe(`connection tests`, {concurrent: true}, () => { expect(memberA?.basename).toBe(`THEMBR.MBR`); }) - test('parseMemberPath (ASP)', () => { + it('parseMemberPath (ASP)', () => { const memberA = connection.parserMemberPath(`/theasp/thelib/thespf/thembr.mbr`); expect(memberA?.asp).toBe(`THEASP`); @@ -95,7 +95,7 @@ describe(`connection tests`, {concurrent: true}, () => { expect(memberA?.basename).toBe(`THEMBR.MBR`); }) - test('parseMemberPath (no root)', () => { + it('parseMemberPath (no root)', () => { const memberA = connection.parserMemberPath(`thelib/thespf/thembr.mbr`); expect(memberA?.asp).toBe(undefined); @@ -106,7 +106,7 @@ describe(`connection tests`, {concurrent: true}, () => { expect(memberA?.basename).toBe(`THEMBR.MBR`); }); - test('parseMemberPath (no extension)', () => { + it('parseMemberPath (no extension)', () => { const memberA = connection.parserMemberPath(`/thelib/thespf/thembr`); expect(memberA?.asp).toBe(undefined); @@ -121,13 +121,13 @@ describe(`connection tests`, {concurrent: true}, () => { ).toThrow(`Source Type extension is required.`); }); - test('parseMemberPath (invalid length)', () => { + it('parseMemberPath (invalid length)', () => { expect( () => { connection.parserMemberPath(`/thespf/thembr.mbr`) } ).toThrow(`Invalid path: /thespf/thembr.mbr. Use format LIB/SPF/NAME.ext`); }); - test('runCommand (ILE)', async () => { + it('runCommand (ILE)', async () => { const result = await connection.runCommand({ command: `DSPJOB OPTION(*DFNA)`, environment: `ile` @@ -137,7 +137,7 @@ describe(`connection tests`, {concurrent: true}, () => { expect(["JOBPTY", "OUTPTY", "ENDSEV", "DDMCNV", "BRKMSG", "STSMSG", "DEVRCYACN", "TSEPOOL", "PRTKEYFMT", "SRTSEQ"].every(attribute => result.stdout.includes(attribute))).toBe(true); }) - test('runCommand (ILE, with error)', async () => { + it('runCommand (ILE, with error)', async () => { const result = await connection.runCommand({ command: `CHKOBJ OBJ(QSYS/NOEXIST) OBJTYPE(*DTAARA)`, noLibList: true @@ -147,7 +147,7 @@ describe(`connection tests`, {concurrent: true}, () => { expect(result?.stderr).toBeTruthy(); }); - test('runCommand (ILE, custom library list)', async () => { const config = connection.getConfig(); + it('runCommand (ILE, custom library list)', async () => { const config = connection.getConfig(); const ogLibl = config!.libraryList.slice(0); @@ -178,7 +178,7 @@ describe(`connection tests`, {concurrent: true}, () => { expect(resultB.stdout.includes(`QSYSINC USR`)).toBe(true); }); - test('runCommand (ILE, library list order from variable)', async () => { + it('runCommand (ILE, library list order from variable)', async () => { const result = await connection?.runCommand({ command: `DSPLIBL`, environment: `ile`, @@ -196,7 +196,7 @@ describe(`connection tests`, {concurrent: true}, () => { expect(qtempIndex < qsysincIndex).toBeTruthy(); }); - test('runCommand (ILE, library order from config)', async () => { const config = connection.getConfig(); + it('runCommand (ILE, library order from config)', async () => { const config = connection.getConfig(); const ogLibl = config!.libraryList.slice(0); @@ -218,7 +218,7 @@ describe(`connection tests`, {concurrent: true}, () => { expect(qtempIndex < qsysincIndex).toBeTruthy(); }); - test('withTempDirectory and countFiles', async () => { const content = connection.getContent()!; + it('withTempDirectory and countFiles', async () => { const content = connection.getContent()!; let temp; await connection.withTempDirectory(async tempDir => { @@ -246,7 +246,7 @@ describe(`connection tests`, {concurrent: true}, () => { } }); - test('upperCaseName', () => { + it('upperCaseName', () => { { const variantsBackup = connection.variantChars.local; From cdd86d42d3363bcf957ad2f9b55601dced6a569a Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 17 Jan 2025 10:34:40 -0500 Subject: [PATCH 23/28] Increase timeout for 'Listing objects with variants' test Signed-off-by: worksofliam --- src/api/tests/suites/encoding.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/tests/suites/encoding.test.ts b/src/api/tests/suites/encoding.test.ts index 8b8b7745e..10183a31e 100644 --- a/src/api/tests/suites/encoding.test.ts +++ b/src/api/tests/suites/encoding.test.ts @@ -176,7 +176,7 @@ describe('Encoding tests', {concurrent: true} ,() => { }); }); - it('Listing objects with variants', async () => { + it('Listing objects with variants',{timeout: 15000}, async () => { const content = connection.getContent(); if (connection && content) { const tempLib = connection.config?.tempLibrary!; From 043822fa2ecc74678e102ccbfd6fc40c164c3486 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 17 Jan 2025 13:19:08 -0500 Subject: [PATCH 24/28] Increase timeout for Content Tests setup Signed-off-by: worksofliam --- src/api/tests/suites/content.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/tests/suites/content.test.ts b/src/api/tests/suites/content.test.ts index 587ab46fd..2a09cb67f 100644 --- a/src/api/tests/suites/content.test.ts +++ b/src/api/tests/suites/content.test.ts @@ -11,7 +11,7 @@ describe('Content Tests', {concurrent: true}, () => { let connection: IBMi beforeAll(async () => { connection = await newConnection(); - }) + }, 25000) afterAll(async () => { disposeConnection(connection); From ea6bfc0b3d54938077d3f83d33f74e48828032bb Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 17 Jan 2025 13:46:32 -0500 Subject: [PATCH 25/28] Tests run concurrently Signed-off-by: worksofliam --- .../tests/{globalSetup.ts => connection.ts} | 25 +++++++++++++------ src/api/tests/env.ts | 11 -------- src/api/tests/setup.ts | 18 +++++++++++++ src/api/tests/suites/components.test.ts | 4 +-- src/api/tests/suites/connection.test.ts | 4 +-- src/api/tests/suites/content.test.ts | 2 +- src/api/tests/suites/encoding.test.ts | 4 +-- src/api/tests/suites/search.test.ts | 4 +-- src/api/tests/testConfigSetup.ts | 6 ++++- vitest.config.ts | 4 +-- 10 files changed, 52 insertions(+), 30 deletions(-) rename src/api/tests/{globalSetup.ts => connection.ts} (79%) delete mode 100644 src/api/tests/env.ts create mode 100644 src/api/tests/setup.ts diff --git a/src/api/tests/globalSetup.ts b/src/api/tests/connection.ts similarity index 79% rename from src/api/tests/globalSetup.ts rename to src/api/tests/connection.ts index 1c5c6440d..20b124851 100644 --- a/src/api/tests/globalSetup.ts +++ b/src/api/tests/connection.ts @@ -1,7 +1,4 @@ -import assert from "assert"; import IBMi from "../IBMi"; -import { ENV_CREDS } from "./env"; -import { afterAll, beforeAll, expect } from "vitest"; import { CodeForIStorage } from "../configuration/storage/CodeForIStorage"; import { CustomQSh } from "../components/cqsh"; import path from "path"; @@ -11,8 +8,17 @@ import { GetNewLibl } from "../components/getNewLibl"; import { extensionComponentRegistry } from "../components/manager"; import { JsonConfig, JsonStorage } from "./testConfigSetup"; +export const testStorage = new JsonStorage(); const testConfig = new JsonConfig(); -const testStorage = new JsonStorage(); + +export const CONNECTION_TIMEOUT = process.env.VITE_CONNECTION_TIMEOUT ? parseInt(process.env.VITE_CONNECTION_TIMEOUT) : 25000; + +const ENV_CREDS = { + host: process.env.VITE_SERVER || `localhost`, + user: process.env.VITE_DB_USER, + password: process.env.VITE_DB_PASS, + port: parseInt(process.env.VITE_DB_PORT || `22`) +} export async function newConnection() { const virtualStorage = testStorage; @@ -62,13 +68,18 @@ export async function newConnection() { } ); - expect(result).toBeDefined(); - expect(result.success).toBeTruthy(); + if (!result.success) { + throw new Error(`Failed to connect to IBMi`); + } return conn; } -export function disposeConnection(conn: IBMi) { +export function disposeConnection(conn?: IBMi) { + if (!conn) { + return; + } + conn.dispose(); testStorage.save(); testConfig.save(); diff --git a/src/api/tests/env.ts b/src/api/tests/env.ts deleted file mode 100644 index 1e5129459..000000000 --- a/src/api/tests/env.ts +++ /dev/null @@ -1,11 +0,0 @@ - -/** - * Default credentials for connecting to the daemon server, - * loaded from environment variables. - */ -export const ENV_CREDS = { - host: process.env.VITE_SERVER || `localhost`, - user: process.env.VITE_DB_USER, - password: process.env.VITE_DB_PASS, - port: parseInt(process.env.VITE_DB_PORT || `22`) -} \ No newline at end of file diff --git a/src/api/tests/setup.ts b/src/api/tests/setup.ts new file mode 100644 index 000000000..43baac0ce --- /dev/null +++ b/src/api/tests/setup.ts @@ -0,0 +1,18 @@ +import type { TestProject } from "vitest/node"; +import { disposeConnection, newConnection, testStorage } from "./connection"; + +export async function setup(project: TestProject) { + // You might pre-connect to simply create the configuration files. + // When the config files exist, it makes future connections just slightly faster. + // Mostly useful during the CI stage. + if (!testStorage.exists()) { + console.log(``); + console.log(`Testing connection before tests run since configs do not exist.`); + + const conn = await newConnection(); + disposeConnection(conn); + + console.log(`Testing connection complete. Configs written.`); + console.log(``); + } +} \ No newline at end of file diff --git a/src/api/tests/suites/components.test.ts b/src/api/tests/suites/components.test.ts index 7feac8ffb..b21c03802 100644 --- a/src/api/tests/suites/components.test.ts +++ b/src/api/tests/suites/components.test.ts @@ -3,13 +3,13 @@ import { GetMemberInfo } from '../../components/getMemberInfo'; import { GetNewLibl } from '../../components/getNewLibl'; import { Tools } from '../../Tools'; import IBMi from '../../IBMi'; -import { disposeConnection, newConnection } from '../globalSetup'; +import { CONNECTION_TIMEOUT, disposeConnection, newConnection } from '../connection'; describe('Component Tests', () => { let connection: IBMi beforeAll(async () => { connection = await newConnection(); - }) + }, CONNECTION_TIMEOUT) afterAll(async () => { disposeConnection(connection); diff --git a/src/api/tests/suites/connection.test.ts b/src/api/tests/suites/connection.test.ts index 3f0c749c5..91bc78a90 100644 --- a/src/api/tests/suites/connection.test.ts +++ b/src/api/tests/suites/connection.test.ts @@ -1,7 +1,7 @@ import { expect, describe, afterAll, beforeAll, it } from 'vitest' import { Tools } from '../../Tools'; -import { disposeConnection, newConnection } from '../globalSetup'; +import { disposeConnection, newConnection } from '../connection'; import IBMi from '../../IBMi'; import { getJavaHome } from '../../configuration/DebugConfiguration'; @@ -9,7 +9,7 @@ describe(`connection tests`, {concurrent: true}, () => { let connection: IBMi beforeAll(async () => { connection = await newConnection(); - }) + }, 25000) afterAll(async () => { disposeConnection(connection); diff --git a/src/api/tests/suites/content.test.ts b/src/api/tests/suites/content.test.ts index 2a09cb67f..168f158de 100644 --- a/src/api/tests/suites/content.test.ts +++ b/src/api/tests/suites/content.test.ts @@ -5,7 +5,7 @@ import tmp from 'tmp'; import { Tools } from '../../Tools'; import { posix } from 'path'; import IBMi from '../../IBMi'; -import { newConnection, disposeConnection } from '../globalSetup'; +import { newConnection, disposeConnection } from '../connection'; describe('Content Tests', {concurrent: true}, () => { let connection: IBMi diff --git a/src/api/tests/suites/encoding.test.ts b/src/api/tests/suites/encoding.test.ts index 10183a31e..a3fe2b123 100644 --- a/src/api/tests/suites/encoding.test.ts +++ b/src/api/tests/suites/encoding.test.ts @@ -3,7 +3,7 @@ import IBMi from "../../IBMi"; import { Tools } from "../../Tools"; import { IBMiObject } from "../../types"; import { describe, it, expect, afterAll, beforeAll } from 'vitest'; -import { newConnection, disposeConnection } from "../globalSetup"; +import { newConnection, disposeConnection } from "../connection"; const contents = { '37': [`Hello world`], @@ -43,7 +43,7 @@ describe('Encoding tests', {concurrent: true} ,() => { let connection: IBMi beforeAll(async () => { connection = await newConnection(); - }) + }, 25000) afterAll(async () => { disposeConnection(connection); diff --git a/src/api/tests/suites/search.test.ts b/src/api/tests/suites/search.test.ts index 2cd829c0d..302aa84a1 100644 --- a/src/api/tests/suites/search.test.ts +++ b/src/api/tests/suites/search.test.ts @@ -2,13 +2,13 @@ import { describe, it, expect, afterAll, beforeAll } from 'vitest'; import { parseFilter } from '../../Filter'; import { Search } from '../../Search'; import IBMi from '../../IBMi'; -import { newConnection, disposeConnection } from '../globalSetup'; +import { newConnection, disposeConnection } from '../connection'; describe('Search Tests', {concurrent: true}, () => { let connection: IBMi beforeAll(async () => { connection = await newConnection(); - }) + }, 25000) afterAll(async () => { disposeConnection(connection); diff --git a/src/api/tests/testConfigSetup.ts b/src/api/tests/testConfigSetup.ts index 21bc2e3e2..b0e55180c 100644 --- a/src/api/tests/testConfigSetup.ts +++ b/src/api/tests/testConfigSetup.ts @@ -44,8 +44,12 @@ export class JsonStorage extends BaseStorage { super(); } + exists() { + return existsSync(storagePath); + } + public async load() { - if (existsSync(storagePath)) { + if (this.exists()) { const data = await import(storagePath); for (const key in data) { this.globalState.set(key, data[key]); diff --git a/vitest.config.ts b/vitest.config.ts index ff1871fb8..6d4a56792 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -4,8 +4,8 @@ import { defineConfig } from 'vite' export default defineConfig({ test: { // ... Specify options here. - fileParallelism: false, root: './src/api', - testTimeout: 10000 + globalSetup: [`tests/setup.ts`], + testTimeout: 10000, }, }) \ No newline at end of file From ff7f4561da82ec1c74d6a12fca67f0933e4933a3 Mon Sep 17 00:00:00 2001 From: LJ <3708366+worksofliam@users.noreply.github.com> Date: Wed, 22 Jan 2025 11:44:37 -0500 Subject: [PATCH 26/28] Update content.test.ts Co-authored-by: Sanjula Ganepola <32170854+SanjulaGanepola@users.noreply.github.com> --- src/api/tests/suites/content.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/tests/suites/content.test.ts b/src/api/tests/suites/content.test.ts index 168f158de..5edaa7fea 100644 --- a/src/api/tests/suites/content.test.ts +++ b/src/api/tests/suites/content.test.ts @@ -430,7 +430,7 @@ describe('Content Tests', {concurrent: true}, () => { expect(members?.length).not.toBe(0); expect(members!.map(m => m.name).every(n => n.startsWith('SYS') || n.startsWith('I') || n.endsWith('EX'))).toBe(true); - const membersRegex = await content?.getMemberList({ library: 'QSYSINC', sourceFile: 'QRPGLESRC', members: '^QSY(?!RTV).*$' }); + const membersRegex = await content?.getMemberList({ library: 'QSYSINC', sourceFile: 'QRPGLESRC', members: '^QSY(?!RTV).*$', filterType: 'regex' }); expect(membersRegex?.length).not.toBe(0); expect(membersRegex!.map(m => m.name).every(n => n.startsWith('QSY') && !n.includes('RTV'))).toBe(true); }); From 005aeed121c2a9c243dd8be1a7ddee23e61e4d52 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Wed, 22 Jan 2025 14:22:01 -0500 Subject: [PATCH 27/28] Fix base storage Signed-off-by: worksofliam --- src/api/tests/testConfigSetup.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/api/tests/testConfigSetup.ts b/src/api/tests/testConfigSetup.ts index b0e55180c..f6a59c757 100644 --- a/src/api/tests/testConfigSetup.ts +++ b/src/api/tests/testConfigSetup.ts @@ -38,10 +38,13 @@ export class JsonConfig extends Config { } export class JsonStorage extends BaseStorage { - protected readonly globalState: Map = new Map(); + protected readonly globalState: Map; constructor() { - super(); + const newState = new Map() + super(newState); + + this.globalState = newState; } exists() { From 19a267354a78e3266c2e1f1d36437e3052792f54 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Wed, 22 Jan 2025 14:22:12 -0500 Subject: [PATCH 28/28] Use variable for timeouts Signed-off-by: worksofliam --- src/api/.env.sample | 3 ++- src/api/tests/suites/connection.test.ts | 4 ++-- src/api/tests/suites/content.test.ts | 4 ++-- src/api/tests/suites/encoding.test.ts | 4 ++-- src/api/tests/suites/search.test.ts | 4 ++-- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/api/.env.sample b/src/api/.env.sample index 7ebbe314f..c952237d7 100644 --- a/src/api/.env.sample +++ b/src/api/.env.sample @@ -1,4 +1,5 @@ VITE_SERVER= VITE_DB_USER= VITE_DB_PASS= -VITE_DB_PORT=22 \ No newline at end of file +VITE_DB_PORT=22 +#VITE_CONNECTION_TIMEOUT=25000 \ No newline at end of file diff --git a/src/api/tests/suites/connection.test.ts b/src/api/tests/suites/connection.test.ts index 91bc78a90..846d3256d 100644 --- a/src/api/tests/suites/connection.test.ts +++ b/src/api/tests/suites/connection.test.ts @@ -1,7 +1,7 @@ import { expect, describe, afterAll, beforeAll, it } from 'vitest' import { Tools } from '../../Tools'; -import { disposeConnection, newConnection } from '../connection'; +import { CONNECTION_TIMEOUT, disposeConnection, newConnection } from '../connection'; import IBMi from '../../IBMi'; import { getJavaHome } from '../../configuration/DebugConfiguration'; @@ -9,7 +9,7 @@ describe(`connection tests`, {concurrent: true}, () => { let connection: IBMi beforeAll(async () => { connection = await newConnection(); - }, 25000) + }, CONNECTION_TIMEOUT) afterAll(async () => { disposeConnection(connection); diff --git a/src/api/tests/suites/content.test.ts b/src/api/tests/suites/content.test.ts index 5edaa7fea..f49360d3c 100644 --- a/src/api/tests/suites/content.test.ts +++ b/src/api/tests/suites/content.test.ts @@ -5,13 +5,13 @@ import tmp from 'tmp'; import { Tools } from '../../Tools'; import { posix } from 'path'; import IBMi from '../../IBMi'; -import { newConnection, disposeConnection } from '../connection'; +import { newConnection, disposeConnection, CONNECTION_TIMEOUT } from '../connection'; describe('Content Tests', {concurrent: true}, () => { let connection: IBMi beforeAll(async () => { connection = await newConnection(); - }, 25000) + }, CONNECTION_TIMEOUT) afterAll(async () => { disposeConnection(connection); diff --git a/src/api/tests/suites/encoding.test.ts b/src/api/tests/suites/encoding.test.ts index a3fe2b123..de5bd03bb 100644 --- a/src/api/tests/suites/encoding.test.ts +++ b/src/api/tests/suites/encoding.test.ts @@ -3,7 +3,7 @@ import IBMi from "../../IBMi"; import { Tools } from "../../Tools"; import { IBMiObject } from "../../types"; import { describe, it, expect, afterAll, beforeAll } from 'vitest'; -import { newConnection, disposeConnection } from "../connection"; +import { newConnection, disposeConnection, CONNECTION_TIMEOUT } from "../connection"; const contents = { '37': [`Hello world`], @@ -43,7 +43,7 @@ describe('Encoding tests', {concurrent: true} ,() => { let connection: IBMi beforeAll(async () => { connection = await newConnection(); - }, 25000) + }, CONNECTION_TIMEOUT) afterAll(async () => { disposeConnection(connection); diff --git a/src/api/tests/suites/search.test.ts b/src/api/tests/suites/search.test.ts index 302aa84a1..ce918704c 100644 --- a/src/api/tests/suites/search.test.ts +++ b/src/api/tests/suites/search.test.ts @@ -2,13 +2,13 @@ import { describe, it, expect, afterAll, beforeAll } from 'vitest'; import { parseFilter } from '../../Filter'; import { Search } from '../../Search'; import IBMi from '../../IBMi'; -import { newConnection, disposeConnection } from '../connection'; +import { newConnection, disposeConnection, CONNECTION_TIMEOUT } from '../connection'; describe('Search Tests', {concurrent: true}, () => { let connection: IBMi beforeAll(async () => { connection = await newConnection(); - }, 25000) + }, CONNECTION_TIMEOUT) afterAll(async () => { disposeConnection(connection);