feat(cli): Improve test runner configuration: update test scripts, reorganize test directories, update dependencies and add local settings for command permissions.
This commit is contained in:
parent
d9e0f1f758
commit
bac2f852c5
@ -1,5 +1,14 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-05-15 - 1.5.0 - feat(cli)
|
||||
Improve test runner configuration: update test scripts, reorganize test directories, update dependencies and add local settings for command permissions.
|
||||
|
||||
- Updated package.json scripts to use pnpm and separate commands for tapbundle and tstest.
|
||||
- Reorganized tests into dedicated directories (test/tapbundle and test/tstest) and removed deprecated test files.
|
||||
- Refactored import paths and bumped dependency versions in tapbundle, tstest, and associated node utilities.
|
||||
- Added .claude/settings.local.json to configure local permissions for bash and web fetch commands.
|
||||
- Introduced ts/tspublish.json to define publish order.
|
||||
|
||||
## 2025-05-15 - 1.4.0 - feat(logging)
|
||||
Display failed test console logs in default mode
|
||||
|
||||
|
24
package.json
24
package.json
@ -12,11 +12,12 @@
|
||||
"tstest": "./cli.js"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "(npm run cleanUp && npm run prepareTest && npm run tstest)",
|
||||
"prepareTest": "git clone https://gitlab.com/sandboxzone/sandbox-npmts.git .nogit/sandbox-npmts && cd .nogit/sandbox-npmts && npm install",
|
||||
"tstest": "cd .nogit/sandbox-npmts && node ../../cli.ts.js test/ --web",
|
||||
"cleanUp": "rm -rf .nogit/sandbox-npmts",
|
||||
"build": "(tsbuild --web --allowimplicitany --skiplibcheck)",
|
||||
"test": "pnpm run build && pnpm run test:tapbundle && pnpm run test:tstest",
|
||||
"test:tapbundle": "tsx ./cli.child.ts test/tapbundle/**/*.ts",
|
||||
"test:tapbundle:verbose": "tsx ./cli.child.ts test/tapbundle/**/*.ts --verbose",
|
||||
"test:tstest": "tsx ./cli.child.ts test/tstest/**/*.ts",
|
||||
"test:tstest:verbose": "tsx ./cli.child.ts test/tstest/**/*.ts --verbose",
|
||||
"build": "(tsbuild tsfolders)",
|
||||
"buildDocs": "tsdoc"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -28,13 +29,22 @@
|
||||
"@git.zone/tsbundle": "^2.2.5",
|
||||
"@git.zone/tsrun": "^1.3.3",
|
||||
"@push.rocks/consolecolor": "^2.0.2",
|
||||
"@push.rocks/qenv": "^6.1.0",
|
||||
"@push.rocks/smartbrowser": "^2.0.8",
|
||||
"@push.rocks/smartcrypto": "^2.0.4",
|
||||
"@push.rocks/smartdelay": "^3.0.5",
|
||||
"@push.rocks/smartenv": "^5.0.12",
|
||||
"@push.rocks/smartexpect": "^2.4.2",
|
||||
"@push.rocks/smartfile": "^11.2.0",
|
||||
"@push.rocks/smartlog": "^3.0.9",
|
||||
"@push.rocks/smartjson": "^5.0.20",
|
||||
"@push.rocks/smartlog": "^3.1.1",
|
||||
"@push.rocks/smartmongo": "^2.0.12",
|
||||
"@push.rocks/smartpath": "^5.0.18",
|
||||
"@push.rocks/smartpromise": "^4.2.3",
|
||||
"@push.rocks/smartrequest": "^2.1.0",
|
||||
"@push.rocks/smarts3": "^2.2.5",
|
||||
"@push.rocks/smartshell": "^3.2.3",
|
||||
"@push.rocks/tapbundle": "^6.0.3",
|
||||
"@push.rocks/smarttime": "^4.1.1",
|
||||
"@types/ws": "^8.18.1",
|
||||
"figures": "^6.1.0",
|
||||
"ws": "^8.18.2"
|
||||
|
1302
pnpm-lock.yaml
generated
1302
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,62 @@
|
||||
# Architecture Overview
|
||||
|
||||
## Project Structure
|
||||
|
||||
This project integrates tstest with tapbundle through a modular architecture:
|
||||
|
||||
1. **tstest** (`/ts/`) - The test runner that discovers and executes test files
|
||||
2. **tapbundle** (`/ts_tapbundle/`) - The TAP testing framework for writing tests
|
||||
3. **tapbundle_node** (`/ts_tapbundle_node/`) - Node.js-specific testing utilities
|
||||
|
||||
## How Components Work Together
|
||||
|
||||
### Test Execution Flow
|
||||
|
||||
1. **CLI Entry Point** (`cli.js` ’ `cli.ts.js` ’ `cli.child.ts`)
|
||||
- The CLI uses tsx to run TypeScript files directly
|
||||
- Accepts glob patterns to find test files
|
||||
- Supports options like `--verbose`, `--quiet`, `--web`
|
||||
|
||||
2. **Test Discovery**
|
||||
- tstest scans for test files matching the provided pattern
|
||||
- Defaults to `test/**/*.ts` when no pattern is specified
|
||||
- Supports both file and directory modes
|
||||
|
||||
3. **Test Runner**
|
||||
- Each test file imports `tap` and `expect` from tapbundle
|
||||
- Tests are written using `tap.test()` with async functions
|
||||
- Browser tests are compiled with esbuild and run in Chromium via Puppeteer
|
||||
|
||||
### Key Integration Points
|
||||
|
||||
1. **Import Structure**
|
||||
- Test files import from local tapbundle: `import { tap, expect } from '../../ts_tapbundle/index.js'`
|
||||
- Node-specific tests also import from tapbundle_node: `import { tapNodeTools } from '../../ts_tapbundle_node/index.js'`
|
||||
|
||||
2. **WebHelpers**
|
||||
- Browser tests can use webhelpers for DOM manipulation
|
||||
- `webhelpers.html` - Template literal for creating HTML strings
|
||||
- `webhelpers.fixture` - Creates DOM elements from HTML strings
|
||||
- Automatically detects browser environment and only enables in browser context
|
||||
|
||||
3. **Build System**
|
||||
- Uses `tsbuild tsfolders` to compile TypeScript
|
||||
- Maintains separate output directories: `/dist_ts/`, `/dist_ts_tapbundle/`, `/dist_ts_tapbundle_node/`
|
||||
- Compilation order is resolved automatically based on dependencies
|
||||
|
||||
### Test Scripts
|
||||
|
||||
The package.json defines several test scripts:
|
||||
- `test` - Builds and runs all tests (tapbundle and tstest)
|
||||
- `test:tapbundle` - Runs tapbundle framework tests
|
||||
- `test:tstest` - Runs tstest's own tests
|
||||
- Both support `:verbose` variants for detailed output
|
||||
|
||||
### Environment Detection
|
||||
|
||||
The framework automatically detects the runtime environment:
|
||||
- Node.js tests run directly via tsx
|
||||
- Browser tests are compiled and served via a local server
|
||||
- WebHelpers are only enabled in browser environment
|
||||
|
||||
This architecture allows for seamless testing across both Node.js and browser environments while maintaining a clean separation of concerns.
|
48
test/tapbundle/test.browser.nonci.ts
Normal file
48
test/tapbundle/test.browser.nonci.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { tap, expect, webhelpers } from '../../ts_tapbundle/index.js';
|
||||
|
||||
tap.preTask('custompretask', async () => {
|
||||
console.log('this is a pretask');
|
||||
});
|
||||
|
||||
tap.test('should have access to webhelpers', async () => {
|
||||
const myElement = await webhelpers.fixture(webhelpers.html`<div></div>`);
|
||||
expect(myElement).toBeInstanceOf(HTMLElement);
|
||||
console.log(myElement);
|
||||
});
|
||||
|
||||
const test1 = tap.test('my first test -> expect true to be true', async () => {
|
||||
return expect(true).toBeTrue();
|
||||
});
|
||||
|
||||
const test2 = tap.test('my second test', async (tools) => {
|
||||
await tools.delayFor(50);
|
||||
});
|
||||
|
||||
const test3 = tap.test(
|
||||
'my third test -> test2 should take longer than test1 and endure at least 1000ms',
|
||||
async () => {
|
||||
expect(
|
||||
(await test1.testPromise).hrtMeasurement.milliSeconds <
|
||||
(await test2).hrtMeasurement.milliSeconds,
|
||||
).toBeTrue();
|
||||
expect((await test2.testPromise).hrtMeasurement.milliSeconds > 10).toBeTrue();
|
||||
},
|
||||
);
|
||||
|
||||
const test4 = tap.skip.test('my 4th test -> should fail', async (tools) => {
|
||||
tools.allowFailure();
|
||||
expect(false).toBeTrue();
|
||||
});
|
||||
|
||||
const test5 = tap.test('my 5th test -> should pass in about 500ms', async (tools) => {
|
||||
tools.timeout(1000);
|
||||
await tools.delayFor(500);
|
||||
});
|
||||
|
||||
const test6 = tap.skip.test('my 6th test -> should fail after 1000ms', async (tools) => {
|
||||
tools.allowFailure();
|
||||
tools.timeout(1000);
|
||||
await tools.delayFor(100);
|
||||
});
|
||||
|
||||
await tap.start();
|
28
test/tapbundle/test.node.ts
Normal file
28
test/tapbundle/test.node.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { tap, expect } from '../../ts_tapbundle/index.js';
|
||||
|
||||
import { tapNodeTools } from '../../ts_tapbundle_node/index.js';
|
||||
|
||||
tap.test('should execure a command', async () => {
|
||||
const result = await tapNodeTools.runCommand('ls -la');
|
||||
expect(result.exitCode).toEqual(0);
|
||||
});
|
||||
|
||||
tap.test('should create a https cert', async () => {
|
||||
const { key, cert } = await tapNodeTools.createHttpsCert('localhost');
|
||||
console.log(key);
|
||||
console.log(cert);
|
||||
expect(key).toInclude('-----BEGIN RSA PRIVATE KEY-----');
|
||||
expect(cert).toInclude('-----BEGIN CERTIFICATE-----');
|
||||
});
|
||||
|
||||
tap.test('should create a smartmongo instance', async () => {
|
||||
const smartmongo = await tapNodeTools.createSmartmongo();
|
||||
await smartmongo.stop();
|
||||
});
|
||||
|
||||
tap.test('should create a smarts3 instance', async () => {
|
||||
const smarts3 = await tapNodeTools.createSmarts3();
|
||||
await smarts3.stop();
|
||||
});
|
||||
|
||||
tap.start();
|
5
test/tapbundle/test.tapwrap.ts
Normal file
5
test/tapbundle/test.tapwrap.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { tap, expect, TapWrap } from '../../ts_tapbundle/index.js';
|
||||
|
||||
tap.test('should run a test', async () => {});
|
||||
|
||||
tap.start();
|
49
test/tapbundle/test.ts
Normal file
49
test/tapbundle/test.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { tap, expect } from '../../ts_tapbundle/index.js';
|
||||
|
||||
tap.preTask('hi there', async () => {
|
||||
console.log('this is a pretask');
|
||||
});
|
||||
|
||||
const test1 = tap.test('my first test -> expect true to be true', async () => {
|
||||
return expect(true).toBeTrue();
|
||||
});
|
||||
|
||||
const test2 = tap.test('my second test', async (tools) => {
|
||||
await tools.delayFor(1000);
|
||||
});
|
||||
|
||||
const test3 = tap.test(
|
||||
'my third test -> test2 should take longer than test1 and endure at least 1000ms',
|
||||
async () => {
|
||||
expect(
|
||||
(await test1.testPromise).hrtMeasurement.milliSeconds <
|
||||
(await test2).hrtMeasurement.milliSeconds,
|
||||
).toBeTrue();
|
||||
expect((await test2.testPromise).hrtMeasurement.milliSeconds > 1000).toBeTrue();
|
||||
},
|
||||
);
|
||||
|
||||
const test4 = tap.test('my 4th test -> should fail', async (tools) => {
|
||||
tools.allowFailure();
|
||||
expect(false).toBeFalse();
|
||||
return 'hello';
|
||||
});
|
||||
|
||||
const test5 = tap.test('my 5th test -> should pass in about 500ms', async (tools) => {
|
||||
const test4Result = await test4.testResultPromise;
|
||||
tools.timeout(1000);
|
||||
await tools.delayFor(500);
|
||||
});
|
||||
|
||||
const test6 = tap.skip.test('my 6th test -> should fail after 1000ms', async (tools) => {
|
||||
tools.allowFailure();
|
||||
tools.timeout(1000);
|
||||
await tools.delayFor(2000);
|
||||
});
|
||||
|
||||
const test7 = tap.test('my 7th test -> should print a colored string', async (tools) => {
|
||||
const cs = await tools.coloredString('hello', 'red', 'cyan');
|
||||
console.log(cs);
|
||||
});
|
||||
|
||||
tap.start();
|
@ -1,6 +0,0 @@
|
||||
import { expect, tap } from '@push.rocks/tapbundle';
|
||||
import * as tstest from '../ts/index.js';
|
||||
|
||||
tap.test('prepare test', async () => {});
|
||||
|
||||
tap.start();
|
@ -1,4 +1,4 @@
|
||||
import { expect, tap } from '@push.rocks/tapbundle';
|
||||
import { expect, tap } from '../../../ts_tapbundle/index.js';
|
||||
|
||||
tap.test('subdirectory test execution', async () => {
|
||||
console.log('This test verifies subdirectory test discovery works');
|
@ -1,4 +1,4 @@
|
||||
import { expect, tap } from '@push.rocks/tapbundle';
|
||||
import { expect, tap } from '../../ts_tapbundle/index.js';
|
||||
|
||||
tap.test('Test with console output', async () => {
|
||||
console.log('Log message 1 from test');
|
@ -1,4 +1,4 @@
|
||||
import { expect, tap } from '@push.rocks/tapbundle';
|
||||
import { expect, tap } from '../../ts_tapbundle/index.js';
|
||||
|
||||
tap.test('This test should fail', async () => {
|
||||
console.log('This test will fail on purpose');
|
@ -1,4 +1,4 @@
|
||||
import { expect, tap } from '@push.rocks/tapbundle';
|
||||
import { expect, tap } from '../../ts_tapbundle/index.js';
|
||||
|
||||
tap.test('Test that will fail with console logs', async () => {
|
||||
console.log('Starting the test...');
|
@ -1,4 +1,4 @@
|
||||
import { expect, tap } from '@push.rocks/tapbundle';
|
||||
import { expect, tap } from '../../ts_tapbundle/index.js';
|
||||
|
||||
tap.test('glob pattern test execution', async () => {
|
||||
console.log('This test verifies glob pattern execution works');
|
@ -1,4 +1,4 @@
|
||||
import { expect, tap } from '@push.rocks/tapbundle';
|
||||
import { expect, tap } from '../../ts_tapbundle/index.js';
|
||||
|
||||
tap.test('single file test execution', async () => {
|
||||
console.log('This test verifies single file execution works');
|
6
test/tstest/test.ts
Normal file
6
test/tstest/test.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { expect, tap } from '../../ts_tapbundle/index.js';
|
||||
import * as tstest from '../../ts/index.js';
|
||||
|
||||
tap.test('prepare test', async () => {});
|
||||
|
||||
tap.start();
|
@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@git.zone/tstest',
|
||||
version: '1.4.0',
|
||||
version: '1.5.0',
|
||||
description: 'a test utility to run tests that match test/**/*.ts'
|
||||
}
|
||||
|
3
ts/tspublish.json
Normal file
3
ts/tspublish.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"order": 2
|
||||
}
|
@ -18,7 +18,7 @@ import * as smartfile from '@push.rocks/smartfile';
|
||||
import * as smartlog from '@push.rocks/smartlog';
|
||||
import * as smartpromise from '@push.rocks/smartpromise';
|
||||
import * as smartshell from '@push.rocks/smartshell';
|
||||
import * as tapbundle from '@push.rocks/tapbundle';
|
||||
import * as tapbundle from '../dist_ts_tapbundle/index.js';
|
||||
|
||||
export {
|
||||
consolecolor,
|
||||
|
8
ts_tapbundle/00_commitinfo_data.ts
Normal file
8
ts_tapbundle/00_commitinfo_data.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* autocreated commitinfo by @push.rocks/commitinfo
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/tapbundle',
|
||||
version: '6.0.3',
|
||||
description: 'A comprehensive testing automation library that provides a wide range of utilities and tools for TAP (Test Anything Protocol) based testing, especially suitable for projects using tapbuffer.'
|
||||
}
|
7
ts_tapbundle/index.ts
Normal file
7
ts_tapbundle/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export { tap } from './tapbundle.classes.tap.js';
|
||||
export { TapWrap } from './tapbundle.classes.tapwrap.js';
|
||||
export { webhelpers } from './webhelpers.js';
|
||||
|
||||
import { expect } from '@push.rocks/smartexpect';
|
||||
|
||||
export { expect };
|
21
ts_tapbundle/tapbundle.classes.pretask.ts
Normal file
21
ts_tapbundle/tapbundle.classes.pretask.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import * as plugins from './tapbundle.plugins.js';
|
||||
import { TapTools } from './tapbundle.classes.taptools.js';
|
||||
|
||||
export interface IPreTaskFunction {
|
||||
(tapTools?: TapTools): Promise<any>;
|
||||
}
|
||||
|
||||
export class PreTask {
|
||||
public description: string;
|
||||
public preTaskFunction: IPreTaskFunction;
|
||||
|
||||
constructor(descriptionArg: string, preTaskFunctionArg: IPreTaskFunction) {
|
||||
this.description = descriptionArg;
|
||||
this.preTaskFunction = preTaskFunctionArg;
|
||||
}
|
||||
|
||||
public async run() {
|
||||
console.log(`::__PRETASK: ${this.description}`);
|
||||
await this.preTaskFunction(new TapTools(null));
|
||||
}
|
||||
}
|
173
ts_tapbundle/tapbundle.classes.tap.ts
Normal file
173
ts_tapbundle/tapbundle.classes.tap.ts
Normal file
@ -0,0 +1,173 @@
|
||||
import * as plugins from './tapbundle.plugins.js';
|
||||
|
||||
import { type IPreTaskFunction, PreTask } from './tapbundle.classes.pretask.js';
|
||||
import { TapTest, type ITestFunction } from './tapbundle.classes.taptest.js';
|
||||
export class Tap<T> {
|
||||
/**
|
||||
* skips a test
|
||||
* tests marked with tap.skip.test() are never executed
|
||||
*/
|
||||
public skip = {
|
||||
test: (descriptionArg: string, functionArg: ITestFunction<T>) => {
|
||||
console.log(`skipped test: ${descriptionArg}`);
|
||||
},
|
||||
testParallel: (descriptionArg: string, functionArg: ITestFunction<T>) => {
|
||||
console.log(`skipped test: ${descriptionArg}`);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* only executes tests marked as ONLY
|
||||
*/
|
||||
public only = {
|
||||
test: (descriptionArg: string, testFunctionArg: ITestFunction<T>) => {
|
||||
this.test(descriptionArg, testFunctionArg, 'only');
|
||||
},
|
||||
};
|
||||
|
||||
private _tapPreTasks: PreTask[] = [];
|
||||
private _tapTests: TapTest<any>[] = [];
|
||||
private _tapTestsOnly: TapTest<any>[] = [];
|
||||
|
||||
/**
|
||||
* Normal test function, will run one by one
|
||||
* @param testDescription - A description of what the test does
|
||||
* @param testFunction - A Function that returns a Promise and resolves or rejects
|
||||
*/
|
||||
public test(
|
||||
testDescription: string,
|
||||
testFunction: ITestFunction<T>,
|
||||
modeArg: 'normal' | 'only' | 'skip' = 'normal',
|
||||
): TapTest<T> {
|
||||
const localTest = new TapTest<T>({
|
||||
description: testDescription,
|
||||
testFunction,
|
||||
parallel: false,
|
||||
});
|
||||
if (modeArg === 'normal') {
|
||||
this._tapTests.push(localTest);
|
||||
} else if (modeArg === 'only') {
|
||||
this._tapTestsOnly.push(localTest);
|
||||
}
|
||||
return localTest;
|
||||
}
|
||||
|
||||
public preTask(descriptionArg: string, functionArg: IPreTaskFunction) {
|
||||
this._tapPreTasks.push(new PreTask(descriptionArg, functionArg));
|
||||
}
|
||||
|
||||
/**
|
||||
* A parallel test that will not be waited for before the next starts.
|
||||
* @param testDescription - A description of what the test does
|
||||
* @param testFunction - A Function that returns a Promise and resolves or rejects
|
||||
*/
|
||||
public testParallel(testDescription: string, testFunction: ITestFunction<T>) {
|
||||
this._tapTests.push(
|
||||
new TapTest({
|
||||
description: testDescription,
|
||||
testFunction,
|
||||
parallel: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* starts the test evaluation
|
||||
*/
|
||||
public async start(optionsArg?: { throwOnError: boolean }) {
|
||||
// lets set the tapbundle promise
|
||||
const smartenvInstance = new plugins.smartenv.Smartenv();
|
||||
smartenvInstance.isBrowser
|
||||
? ((globalThis as any).tapbundleDeferred = plugins.smartpromise.defer())
|
||||
: null;
|
||||
|
||||
// lets continue with running the tests
|
||||
const promiseArray: Array<Promise<any>> = [];
|
||||
|
||||
// safeguard against empty test array
|
||||
if (this._tapTests.length === 0) {
|
||||
console.log('no tests specified. Ending here!');
|
||||
// TODO: throw proper error
|
||||
return;
|
||||
}
|
||||
|
||||
// determine which tests to run
|
||||
let concerningTests: TapTest[];
|
||||
if (this._tapTestsOnly.length > 0) {
|
||||
concerningTests = this._tapTestsOnly;
|
||||
} else {
|
||||
concerningTests = this._tapTests;
|
||||
}
|
||||
|
||||
// lets run the pretasks
|
||||
for (const preTask of this._tapPreTasks) {
|
||||
await preTask.run();
|
||||
}
|
||||
|
||||
console.log(`1..${concerningTests.length}`);
|
||||
for (let testKey = 0; testKey < concerningTests.length; testKey++) {
|
||||
const currentTest = concerningTests[testKey];
|
||||
const testPromise = currentTest.run(testKey);
|
||||
if (currentTest.parallel) {
|
||||
promiseArray.push(testPromise);
|
||||
} else {
|
||||
await testPromise;
|
||||
}
|
||||
}
|
||||
await Promise.all(promiseArray);
|
||||
|
||||
// when tests have been run and all promises are fullfilled
|
||||
const failReasons: string[] = [];
|
||||
const executionNotes: string[] = [];
|
||||
// collect failed tests
|
||||
for (const tapTest of concerningTests) {
|
||||
if (tapTest.status !== 'success') {
|
||||
failReasons.push(
|
||||
`Test ${tapTest.testKey + 1} failed with status ${tapTest.status}:\n` +
|
||||
`|| ${tapTest.description}\n` +
|
||||
`|| for more information please take a look the logs above`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// render fail Reasons
|
||||
for (const failReason of failReasons) {
|
||||
console.log(failReason);
|
||||
}
|
||||
|
||||
if (optionsArg && optionsArg.throwOnError && failReasons.length > 0) {
|
||||
if (!smartenvInstance.isBrowser) process.exit(1);
|
||||
}
|
||||
if (smartenvInstance.isBrowser) {
|
||||
(globalThis as any).tapbundleDeferred.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
public async stopForcefully(codeArg = 0, directArg = false) {
|
||||
console.log(`tap stopping forcefully! Code: ${codeArg} / Direct: ${directArg}`);
|
||||
if (directArg) {
|
||||
process.exit(codeArg);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
process.exit(codeArg);
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* handle errors
|
||||
*/
|
||||
public threw(err: Error) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly fail the current test with a custom message
|
||||
* @param message - The failure message to display
|
||||
*/
|
||||
public fail(message: string = 'Test failed'): never {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
export let tap = new Tap();
|
87
ts_tapbundle/tapbundle.classes.taptest.ts
Normal file
87
ts_tapbundle/tapbundle.classes.taptest.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import * as plugins from './tapbundle.plugins.js';
|
||||
import { tapCreator } from './tapbundle.tapcreator.js';
|
||||
import { TapTools } from './tapbundle.classes.taptools.js';
|
||||
|
||||
// imported interfaces
|
||||
import { Deferred } from '@push.rocks/smartpromise';
|
||||
import { HrtMeasurement } from '@push.rocks/smarttime';
|
||||
|
||||
// interfaces
|
||||
export type TTestStatus = 'success' | 'error' | 'pending' | 'errorAfterSuccess' | 'timeout';
|
||||
|
||||
export interface ITestFunction<T> {
|
||||
(tapTools?: TapTools): Promise<T>;
|
||||
}
|
||||
|
||||
export class TapTest<T = unknown> {
|
||||
public description: string;
|
||||
public failureAllowed: boolean;
|
||||
public hrtMeasurement: HrtMeasurement;
|
||||
public parallel: boolean;
|
||||
public status: TTestStatus;
|
||||
public tapTools: TapTools;
|
||||
public testFunction: ITestFunction<T>;
|
||||
public testKey: number; // the testKey the position in the test qeue. Set upon calling .run()
|
||||
private testDeferred: Deferred<TapTest<T>> = plugins.smartpromise.defer();
|
||||
public testPromise: Promise<TapTest<T>> = this.testDeferred.promise;
|
||||
private testResultDeferred: Deferred<T> = plugins.smartpromise.defer();
|
||||
public testResultPromise: Promise<T> = this.testResultDeferred.promise;
|
||||
/**
|
||||
* constructor
|
||||
*/
|
||||
constructor(optionsArg: {
|
||||
description: string;
|
||||
testFunction: ITestFunction<T>;
|
||||
parallel: boolean;
|
||||
}) {
|
||||
this.description = optionsArg.description;
|
||||
this.hrtMeasurement = new HrtMeasurement();
|
||||
this.parallel = optionsArg.parallel;
|
||||
this.status = 'pending';
|
||||
this.tapTools = new TapTools(this);
|
||||
this.testFunction = optionsArg.testFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* run the test
|
||||
*/
|
||||
public async run(testKeyArg: number) {
|
||||
this.hrtMeasurement.start();
|
||||
this.testKey = testKeyArg;
|
||||
const testNumber = testKeyArg + 1;
|
||||
try {
|
||||
const testReturnValue = await this.testFunction(this.tapTools);
|
||||
if (this.status === 'timeout') {
|
||||
throw new Error('Test succeeded, but timed out...');
|
||||
}
|
||||
this.hrtMeasurement.stop();
|
||||
console.log(
|
||||
`ok ${testNumber} - ${this.description} # time=${this.hrtMeasurement.milliSeconds}ms`,
|
||||
);
|
||||
this.status = 'success';
|
||||
this.testDeferred.resolve(this);
|
||||
this.testResultDeferred.resolve(testReturnValue);
|
||||
} catch (err: any) {
|
||||
this.hrtMeasurement.stop();
|
||||
console.log(
|
||||
`not ok ${testNumber} - ${this.description} # time=${this.hrtMeasurement.milliSeconds}ms`,
|
||||
);
|
||||
this.testDeferred.resolve(this);
|
||||
this.testResultDeferred.resolve(err);
|
||||
|
||||
// if the test has already succeeded before
|
||||
if (this.status === 'success') {
|
||||
this.status = 'errorAfterSuccess';
|
||||
console.log('!!! ALERT !!!: weird behaviour, since test has been already successfull');
|
||||
} else {
|
||||
this.status = 'error';
|
||||
}
|
||||
|
||||
// if the test is allowed to fail
|
||||
if (this.failureAllowed) {
|
||||
console.log(`please note: failure allowed!`);
|
||||
}
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
}
|
68
ts_tapbundle/tapbundle.classes.taptools.ts
Normal file
68
ts_tapbundle/tapbundle.classes.taptools.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import * as plugins from './tapbundle.plugins.js';
|
||||
import { TapTest } from './tapbundle.classes.taptest.js';
|
||||
|
||||
export interface IPromiseFunc {
|
||||
(): Promise<any>;
|
||||
}
|
||||
|
||||
export class TapTools {
|
||||
/**
|
||||
* the referenced TapTest
|
||||
*/
|
||||
private _tapTest: TapTest;
|
||||
|
||||
constructor(TapTestArg: TapTest<any>) {
|
||||
this._tapTest = TapTestArg;
|
||||
}
|
||||
|
||||
/**
|
||||
* allow failure
|
||||
*/
|
||||
public allowFailure() {
|
||||
this._tapTest.failureAllowed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* async/await delay method
|
||||
*/
|
||||
public async delayFor(timeMilliArg: number) {
|
||||
await plugins.smartdelay.delayFor(timeMilliArg);
|
||||
}
|
||||
|
||||
public async delayForRandom(timeMilliMinArg: number, timeMilliMaxArg: number) {
|
||||
await plugins.smartdelay.delayForRandom(timeMilliMinArg, timeMilliMaxArg);
|
||||
}
|
||||
|
||||
public async coloredString(...args: Parameters<typeof plugins.consolecolor.coloredString>) {
|
||||
return plugins.consolecolor.coloredString(...args);
|
||||
}
|
||||
|
||||
public async timeout(timeMilliArg: number) {
|
||||
const timeout = new plugins.smartdelay.Timeout(timeMilliArg);
|
||||
timeout.makeUnrefed();
|
||||
await timeout.promise;
|
||||
if (this._tapTest.status === 'pending') {
|
||||
this._tapTest.status = 'timeout';
|
||||
}
|
||||
}
|
||||
|
||||
public async returnError(throwingFuncArg: IPromiseFunc) {
|
||||
let funcErr: Error;
|
||||
try {
|
||||
await throwingFuncArg();
|
||||
} catch (err: any) {
|
||||
funcErr = err;
|
||||
}
|
||||
return funcErr;
|
||||
}
|
||||
|
||||
public defer() {
|
||||
return plugins.smartpromise.defer();
|
||||
}
|
||||
|
||||
public cumulativeDefer() {
|
||||
return plugins.smartpromise.cumulativeDefer();
|
||||
}
|
||||
|
||||
public smartjson = plugins.smartjson;
|
||||
}
|
13
ts_tapbundle/tapbundle.classes.tapwrap.ts
Normal file
13
ts_tapbundle/tapbundle.classes.tapwrap.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import * as plugins from './tapbundle.plugins.js';
|
||||
|
||||
export interface ITapWrapOptions {
|
||||
before: () => Promise<any>;
|
||||
after: () => {};
|
||||
}
|
||||
|
||||
export class TapWrap {
|
||||
public options: ITapWrapOptions;
|
||||
constructor(optionsArg: ITapWrapOptions) {
|
||||
this.options = optionsArg;
|
||||
}
|
||||
}
|
9
ts_tapbundle/tapbundle.plugins.ts
Normal file
9
ts_tapbundle/tapbundle.plugins.ts
Normal file
@ -0,0 +1,9 @@
|
||||
// pushrocks
|
||||
import * as consolecolor from '@push.rocks/consolecolor';
|
||||
import * as smartdelay from '@push.rocks/smartdelay';
|
||||
import * as smartenv from '@push.rocks/smartenv';
|
||||
import * as smartexpect from '@push.rocks/smartexpect';
|
||||
import * as smartjson from '@push.rocks/smartjson';
|
||||
import * as smartpromise from '@push.rocks/smartpromise';
|
||||
|
||||
export { consolecolor, smartdelay, smartenv, smartexpect, smartjson, smartpromise };
|
7
ts_tapbundle/tapbundle.tapcreator.ts
Normal file
7
ts_tapbundle/tapbundle.tapcreator.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import * as plugins from './tapbundle.plugins.js';
|
||||
|
||||
export class TapCreator {
|
||||
// TODO:
|
||||
}
|
||||
|
||||
export let tapCreator = new TapCreator();
|
3
ts_tapbundle/tspublish.json
Normal file
3
ts_tapbundle/tspublish.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"order": 1
|
||||
}
|
40
ts_tapbundle/webhelpers.ts
Normal file
40
ts_tapbundle/webhelpers.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import * as plugins from './tapbundle.plugins.js';
|
||||
import { tap } from './tapbundle.classes.tap.js';
|
||||
|
||||
class WebHelpers {
|
||||
html: any;
|
||||
fixture: any;
|
||||
|
||||
constructor() {
|
||||
const smartenv = new plugins.smartenv.Smartenv();
|
||||
|
||||
// Initialize HTML template tag function
|
||||
this.html = (strings: TemplateStringsArray, ...values: any[]) => {
|
||||
let result = '';
|
||||
for (let i = 0; i < strings.length; i++) {
|
||||
result += strings[i];
|
||||
if (i < values.length) {
|
||||
result += values[i];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// Initialize fixture function based on environment
|
||||
if (smartenv.isBrowser) {
|
||||
this.fixture = async (htmlString: string): Promise<HTMLElement> => {
|
||||
const container = document.createElement('div');
|
||||
container.innerHTML = htmlString.trim();
|
||||
const element = container.firstChild as HTMLElement;
|
||||
return element;
|
||||
};
|
||||
} else {
|
||||
// Node.js environment - provide a stub or alternative implementation
|
||||
this.fixture = async (htmlString: string): Promise<any> => {
|
||||
throw new Error('WebHelpers.fixture is only available in browser environment');
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const webhelpers = new WebHelpers();
|
98
ts_tapbundle_node/classes.tapnodetools.ts
Normal file
98
ts_tapbundle_node/classes.tapnodetools.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import { TestFileProvider } from './classes.testfileprovider.js';
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
class TapNodeTools {
|
||||
private smartshellInstance: plugins.smartshell.Smartshell;
|
||||
public testFileProvider = new TestFileProvider();
|
||||
|
||||
constructor() {}
|
||||
|
||||
private qenv: plugins.qenv.Qenv;
|
||||
public async getQenv(): Promise<plugins.qenv.Qenv> {
|
||||
this.qenv = this.qenv || new plugins.qenv.Qenv('./', '.nogit/');
|
||||
return this.qenv;
|
||||
}
|
||||
public async getEnvVarOnDemand(envVarNameArg: string): Promise<string> {
|
||||
const qenv = await this.getQenv();
|
||||
return qenv.getEnvVarOnDemand(envVarNameArg);
|
||||
}
|
||||
|
||||
public async runCommand(commandArg: string): Promise<any> {
|
||||
if (!this.smartshellInstance) {
|
||||
this.smartshellInstance = new plugins.smartshell.Smartshell({
|
||||
executor: 'bash',
|
||||
});
|
||||
}
|
||||
const result = await this.smartshellInstance.exec(commandArg);
|
||||
return result;
|
||||
}
|
||||
|
||||
public async createHttpsCert(
|
||||
commonName: string = 'localhost',
|
||||
allowSelfSigned: boolean = true
|
||||
): Promise<{ key: string; cert: string }> {
|
||||
if (allowSelfSigned) {
|
||||
// set node to allow self-signed certificates
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||
}
|
||||
|
||||
// Generate a key pair
|
||||
const keys = plugins.smartcrypto.nodeForge.pki.rsa.generateKeyPair(2048);
|
||||
|
||||
// Create a self-signed certificate
|
||||
const cert = plugins.smartcrypto.nodeForge.pki.createCertificate();
|
||||
cert.publicKey = keys.publicKey;
|
||||
cert.serialNumber = '01';
|
||||
cert.validity.notBefore = new Date();
|
||||
cert.validity.notAfter = new Date();
|
||||
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
|
||||
|
||||
const attrs = [
|
||||
{ name: 'commonName', value: commonName },
|
||||
{ name: 'countryName', value: 'US' },
|
||||
{ shortName: 'ST', value: 'California' },
|
||||
{ name: 'localityName', value: 'San Francisco' },
|
||||
{ name: 'organizationName', value: 'My Company' },
|
||||
{ shortName: 'OU', value: 'Dev' },
|
||||
];
|
||||
cert.setSubject(attrs);
|
||||
cert.setIssuer(attrs);
|
||||
|
||||
// Sign the certificate with its own private key (self-signed)
|
||||
cert.sign(keys.privateKey, plugins.smartcrypto.nodeForge.md.sha256.create());
|
||||
|
||||
// PEM encode the private key and certificate
|
||||
const pemKey = plugins.smartcrypto.nodeForge.pki.privateKeyToPem(keys.privateKey);
|
||||
const pemCert = plugins.smartcrypto.nodeForge.pki.certificateToPem(cert);
|
||||
|
||||
return {
|
||||
key: pemKey,
|
||||
cert: pemCert,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* create and return a smartmongo instance
|
||||
*/
|
||||
public async createSmartmongo() {
|
||||
const smartmongoMod = await import('@push.rocks/smartmongo');
|
||||
const smartmongoInstance = new smartmongoMod.SmartMongo();
|
||||
await smartmongoInstance.start();
|
||||
return smartmongoInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* create and return a smarts3 instance
|
||||
*/
|
||||
public async createSmarts3() {
|
||||
const smarts3Mod = await import('@push.rocks/smarts3');
|
||||
const smarts3Instance = new smarts3Mod.Smarts3({
|
||||
port: 3003,
|
||||
cleanSlate: true,
|
||||
});
|
||||
await smarts3Instance.start();
|
||||
return smarts3Instance;
|
||||
}
|
||||
}
|
||||
|
||||
export const tapNodeTools = new TapNodeTools();
|
17
ts_tapbundle_node/classes.testfileprovider.ts
Normal file
17
ts_tapbundle_node/classes.testfileprovider.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as paths from './paths.js';
|
||||
|
||||
export const fileUrls = {
|
||||
dockerAlpineImage: 'https://code.foss.global/testassets/docker/raw/branch/main/alpine.tar',
|
||||
}
|
||||
|
||||
export class TestFileProvider {
|
||||
public async getDockerAlpineImageAsLocalTarball(): Promise<string> {
|
||||
const filePath = plugins.path.join(paths.testFilesDir, 'alpine.tar')
|
||||
// fetch the docker alpine image
|
||||
const response = await plugins.smartrequest.getBinary(fileUrls.dockerAlpineImage);
|
||||
await plugins.smartfile.fs.ensureDir(paths.testFilesDir);
|
||||
await plugins.smartfile.memory.toFs(response.body, filePath);
|
||||
return filePath;
|
||||
}
|
||||
}
|
2
ts_tapbundle_node/index.ts
Normal file
2
ts_tapbundle_node/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './classes.tapnodetools.js';
|
||||
|
4
ts_tapbundle_node/paths.ts
Normal file
4
ts_tapbundle_node/paths.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
export const cwd = process.cwd();
|
||||
export const testFilesDir = plugins.path.join(cwd, './.nogit/testfiles/');
|
16
ts_tapbundle_node/plugins.ts
Normal file
16
ts_tapbundle_node/plugins.ts
Normal file
@ -0,0 +1,16 @@
|
||||
// node native
|
||||
import * as crypto from 'crypto';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
export { crypto,fs, path, };
|
||||
|
||||
// @push.rocks scope
|
||||
import * as qenv from '@push.rocks/qenv';
|
||||
import * as smartcrypto from '@push.rocks/smartcrypto';
|
||||
import * as smartfile from '@push.rocks/smartfile';
|
||||
import * as smartpath from '@push.rocks/smartpath';
|
||||
import * as smartrequest from '@push.rocks/smartrequest';
|
||||
import * as smartshell from '@push.rocks/smartshell';
|
||||
|
||||
export { qenv, smartcrypto, smartfile, smartpath, smartrequest, smartshell, };
|
Loading…
x
Reference in New Issue
Block a user