diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 000000000..f87a2c379 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,24 @@ +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 + 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 }} \ No newline at end of file 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/IBMi.ts b/src/api/IBMi.ts index c9915f685..4644f3bc4 100644 --- a/src/api/IBMi.ts +++ b/src/api/IBMi.ts @@ -1098,12 +1098,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/.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/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/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/connection.ts b/src/api/tests/connection.ts new file mode 100644 index 000000000..20b124851 --- /dev/null +++ b/src/api/tests/connection.ts @@ -0,0 +1,86 @@ +import IBMi from "../IBMi"; +import { CodeForIStorage } from "../configuration/storage/CodeForIStorage"; +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"; +import { JsonConfig, JsonStorage } from "./testConfigSetup"; + +export const testStorage = new JsonStorage(); +const testConfig = new JsonConfig(); + +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; + + IBMi.GlobalStorage = new CodeForIStorage(virtualStorage); + 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(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`, + username: ENV_CREDS.user!, + password: ENV_CREDS.password!, + port: ENV_CREDS.port + }; + + // Override this so not to spam the console. + conn.appendOutput = (data) => {}; + + const result = await conn.connect( + creds, + { + message: (type: string, message: string) => { + // console.log(`${type.padEnd(10)} ${message}`); + }, + progress: ({message}) => { + // console.log(`PROGRESS: ${message}`); + }, + uiErrorHandler: async (connection, code, data) => { + console.log(`Connection warning: ${code}: ${JSON.stringify(data)}`); + return false; + }, + } + ); + + if (!result.success) { + throw new Error(`Failed to connect to IBMi`); + } + + return conn; +} + +export function disposeConnection(conn?: IBMi) { + if (!conn) { + return; + } + + conn.dispose(); + testStorage.save(); + testConfig.save(); +} \ No newline at end of file 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/globalSetup.ts b/src/api/tests/globalSetup.ts deleted file mode 100644 index 9a25a96ad..000000000 --- a/src/api/tests/globalSetup.ts +++ /dev/null @@ -1,59 +0,0 @@ -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"; - -beforeAll(async () => { - const virtualStorage = new VirtualStorage(); - - IBMi.GlobalStorage = new CodeForIStorage(virtualStorage); - IBMi.connectionManager.configMethod = new VirtualConfig(); - - const conn = new IBMi(); - - const creds = { - host: ENV_CREDS.host!, - name: `testsystem`, - username: ENV_CREDS.user!, - password: ENV_CREDS.password!, - port: ENV_CREDS.port - }; - - // Override this so not to spam the console. - conn.appendOutput = (data) => {}; - - const result = await conn.connect( - creds, - { - message: (type: string, message: string) => { - // console.log(`${type.padEnd(10)} ${message}`); - }, - progress: ({message}) => { - // console.log(`PROGRESS: ${message}`); - }, - uiErrorHandler: async (connection, code, data) => { - console.log(`UI ERROR: ${code}: ${data}`); - return false; - }, - } - ); - - expect(result).toBeDefined(); - expect(result.success).toBeTruthy(); - - setConnection(conn); -}, 25000); - -afterAll(async () => { - const conn = getConnection(); - - if (conn) { - await conn.dispose(); - } else { - assert.fail(`Connection was not set`); - } -}) \ 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/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/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 diff --git a/src/api/tests/suites/components.test.ts b/src/api/tests/suites/components.test.ts new file mode 100644 index 000000000..b21c03802 --- /dev/null +++ b/src/api/tests/suites/components.test.ts @@ -0,0 +1,57 @@ +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { GetMemberInfo } from '../../components/getMemberInfo'; +import { GetNewLibl } from '../../components/getNewLibl'; +import { Tools } from '../../Tools'; +import IBMi from '../../IBMi'; +import { CONNECTION_TIMEOUT, disposeConnection, newConnection } from '../connection'; + +describe('Component Tests', () => { + let connection: IBMi + beforeAll(async () => { + connection = await newConnection(); + }, CONNECTION_TIMEOUT) + + afterAll(async () => { + disposeConnection(connection); + }); + + it('Get new libl', async () => { + 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 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/connection.test.ts b/src/api/tests/suites/connection.test.ts new file mode 100644 index 000000000..846d3256d --- /dev/null +++ b/src/api/tests/suites/connection.test.ts @@ -0,0 +1,290 @@ + +import { expect, describe, afterAll, beforeAll, it } from 'vitest' +import { Tools } from '../../Tools'; +import { CONNECTION_TIMEOUT, disposeConnection, newConnection } from '../connection'; +import IBMi from '../../IBMi'; +import { getJavaHome } from '../../configuration/DebugConfiguration'; + +describe(`connection tests`, {concurrent: true}, () => { + let connection: IBMi + beforeAll(async () => { + connection = await newConnection(); + }, CONNECTION_TIMEOUT) + + afterAll(async () => { + disposeConnection(connection); + }); + + it('sendCommand', async () => { + const result = await connection.sendCommand({ + command: `echo "Hello world"`, + }); + + expect(result.code).toBe(0); + expect(result.stdout).toBe('Hello world'); + }) + + it('sendCommand with home directory', async () => { + 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'); + }); + + it('sendCommand with environment variables', async () => { + 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'); + }); + + it('getTempRemote', () => { + 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); + }) + + it('parseMemberPath (simple)', () => { + 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`); + }) + + it('parseMemberPath (ASP)', () => { + 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`); + }) + + it('parseMemberPath (no root)', () => { + 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`); + }); + + it('parseMemberPath (no extension)', () => { + 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.`); + }); + + it('parseMemberPath (invalid length)', () => { + expect( + () => { connection.parserMemberPath(`/thespf/thembr.mbr`) } + ).toThrow(`Invalid path: /thespf/thembr.mbr. Use format LIB/SPF/NAME.ext`); + }); + + it('runCommand (ILE)', async () => { + 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); + }) + + it('runCommand (ILE, with error)', async () => { + const result = await connection.runCommand({ + command: `CHKOBJ OBJ(QSYS/NOEXIST) OBJTYPE(*DTAARA)`, + noLibList: true + }); + + expect(result?.code).not.toBe(0); + expect(result?.stderr).toBeTruthy(); + }); + + it('runCommand (ILE, custom library list)', async () => { 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); + }); + + it('runCommand (ILE, library list order from variable)', async () => { + 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(); + }); + + it('runCommand (ILE, library order from config)', async () => { 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(); + }); + + it('withTempDirectory and countFiles', async () => { 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); + } + }); + + it('upperCaseName', () => { + { + 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; + } + } + }); + + 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 new file mode 100644 index 000000000..f49360d3c --- /dev/null +++ b/src/api/tests/suites/content.test.ts @@ -0,0 +1,645 @@ + +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, CONNECTION_TIMEOUT } from '../connection'; + +describe('Content Tests', {concurrent: true}, () => { + let connection: IBMi + beforeAll(async () => { + connection = await newConnection(); + }, CONNECTION_TIMEOUT) + + afterAll(async () => { + disposeConnection(connection); + }); + + it('memberResolve', async () => { + 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' + }); + }); + + it('memberResolve (with invalid ASP)', async () => { + 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' + }); + }); + + it('memberResolve with variants', async () => { + 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' + }); + }); + + it('memberResolve with bad name', async () => { + 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(); + }); + + it('objectResolve .FILE', async () => { + const content = connection.getContent(); + + const lib = await content?.objectResolve('MIH', [ + 'QSYS2', // Doesn't exist here + 'QSYSINC' // Does exist + ]); + + expect(lib).toBe('QSYSINC'); + }); + + it('objectResolve .PGM', async () => { + const content = connection.getContent(); + + const lib = await content?.objectResolve('CMRCV', [ + 'QSYSINC', // Doesn't exist here + 'QSYS2' // Does exist + ]); + + expect(lib).toBe('QSYS2'); + }); + + it('objectResolve .DTAARA with variants', async () => { + 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' + }); + }); + + it('objectResolve with bad name', async () => { + 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(); + }); + + it('streamfileResolve', async () => { + const content = connection.getContent(); + + const streamfilePath = await content?.streamfileResolve(['git'], ['/QOpenSys/pkgs/sbin', '/QOpenSys/pkgs/bin']); + + expect(streamfilePath).toBe('/QOpenSys/pkgs/bin/git'); + }); + + it('streamfileResolve with bad name', async () => { + const content = connection.getContent(); + + const streamfilePath = await content?.streamfileResolve(['sup'], ['/QOpenSys/pkgs/sbin', '/QOpenSys/pkgs/bin']); + + expect(streamfilePath).toBeUndefined(); + }); + + it('streamfileResolve with multiple names', async () => { + const content = connection.getContent(); + + const streamfilePath = await content?.streamfileResolve(['sup', 'sup2', 'git'], ['/QOpenSys/pkgs/sbin', '/QOpenSys/pkgs/bin']); + + expect(streamfilePath).toBe('/QOpenSys/pkgs/bin/git'); + }); + + it('streamfileResolve with blanks in names', async () => { + 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); + } + }); + + it('Test downloadMemberContent', async () => { + const content = connection.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 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 () => { + + 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 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 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 = connection.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 = connection.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 = connection.getContent(); + + const objects = await content?.getFileList(`/tmp/${Date.now()}`); + + expect(objects?.length).toBe(0); + }); + + it('Test getFileList (special chars)', async () => { + 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]}`; + + 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 = connection.getContent(); + + const objects = await content?.getObjectList({ library: 'QSYSINC' }); + + expect(objects?.length).not.toBe(0); + }); + + it('Test getObjectList (pgm filter)', async () => { + const content = connection.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 = connection.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 = connection.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 = connection.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 = connection.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 = connection.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 = connection.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 = connection.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 = connection.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).*$', filterType: 'regex' }); + 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 = connection.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 = connection.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 = 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 = connection.getContent(); + + const exists = await content?.checkObject({ library: 'QSYSINC', name: 'H', type: '*FILE', member: 'MATH' }); + expect(exists).toBeTruthy(); + }); + + it('Check getMemberInfo', async () => { + const content = connection.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 = 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';`); + + 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 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 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 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 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}"`); + } + }); +}); diff --git a/src/api/tests/suites/encoding.test.ts b/src/api/tests/suites/encoding.test.ts new file mode 100644 index 000000000..de5bd03bb --- /dev/null +++ b/src/api/tests/suites/encoding.test.ts @@ -0,0 +1,343 @@ +import path from "path"; +import IBMi from "../../IBMi"; +import { Tools } from "../../Tools"; +import { IBMiObject } from "../../types"; +import { describe, it, expect, afterAll, beforeAll } from 'vitest'; +import { newConnection, disposeConnection, CONNECTION_TIMEOUT } from "../connection"; + +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', {concurrent: true} ,() => { + let connection: IBMi + beforeAll(async () => { + connection = await newConnection(); + }, CONNECTION_TIMEOUT) + + 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) { + 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 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 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 () => { + 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 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 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',{timeout: 15000}, async () => { + const content = connection.getContent(); + 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 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 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/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/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/api/tests/suites/search.test.ts b/src/api/tests/suites/search.test.ts new file mode 100644 index 000000000..ce918704c --- /dev/null +++ b/src/api/tests/suites/search.test.ts @@ -0,0 +1,57 @@ +import { describe, it, expect, afterAll, beforeAll } from 'vitest'; +import { parseFilter } from '../../Filter'; +import { Search } from '../../Search'; +import IBMi from '../../IBMi'; +import { newConnection, disposeConnection, CONNECTION_TIMEOUT } from '../connection'; + +describe('Search Tests', {concurrent: true}, () => { + let connection: IBMi + beforeAll(async () => { + connection = await newConnection(); + }, CONNECTION_TIMEOUT) + + afterAll(async () => { + disposeConnection(connection); + }); + + it('Single member search', async () => { + 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; + 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(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); + }); + + 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 connection.getContent().getMemberList({ library, sourceFile, members: memberFilter }); + expect(checkNames(members.map(member => member.name))).toBe(true); + + 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/src/api/tests/testConfigSetup.ts b/src/api/tests/testConfigSetup.ts new file mode 100644 index 000000000..f6a59c757 --- /dev/null +++ b/src/api/tests/testConfigSetup.ts @@ -0,0 +1,71 @@ +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; + + constructor() { + const newState = new Map() + super(newState); + + this.globalState = newState; + } + + exists() { + return existsSync(storagePath); + } + + public async load() { + if (this.exists()) { + 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 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/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; - } - } - } - ] -}; 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}"`); - } - } - } ] }; 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 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) 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 6f5da3b1c..97a9438ff 100644 --- a/src/testing/index.ts +++ b/src/testing/index.ts @@ -2,15 +2,9 @@ import { env } from "process"; 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"; -import { ILEErrorSuite } from "./ileErrors"; -import { SearchSuite } from "./search"; import { StorageSuite } from "./storage"; import { TestSuitesTreeProvider } from "./testCasesTree"; import { ToolsSuite } from "./tools"; @@ -18,17 +12,11 @@ import { Server } from "../typings"; const suites: TestSuite[] = [ ActionSuite, - ConnectionSuite, ContentSuite, - DebugSuite, DeployToolsSuite, ToolsSuite, - ILEErrorSuite, - FilterSuite, - SearchSuite, StorageSuite, - EncodingSuite, - ComponentSuite + EncodingSuite ] export type TestSuite = { 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 diff --git a/vitest.config.ts b/vitest.config.ts index c22fd1b0c..6d4a56792 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -2,12 +2,10 @@ import { defineConfig } from 'vite' export default defineConfig({ - test: { + test: { // ... Specify options here. - fileParallelism: false, root: './src/api', - setupFiles: [ - 'tests/globalSetup.ts', - ], + globalSetup: [`tests/setup.ts`], + testTimeout: 10000, }, }) \ No newline at end of file