diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5312e0c..64e8cae 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,4 @@ -name: End-to-end test +name: Integration and end-to-end tests on: pull_request: @@ -10,7 +10,7 @@ on: default: false jobs: - e2e-test: + tests: runs-on: ubuntu-latest steps: @@ -27,11 +27,17 @@ jobs: with: node-version: 18 - - name: Install system dependencies for end-to-end tests + - name: Install system dependencies for tests run: build/ci/install-dependencies.sh shell: bash - - name: Enable perf tests + - name: Run integration tests + run: | + # Ensure only integration tests that are supported in CI are run using + # the --ci flag. + make integration-test INTEGRATION_FLAGS="--ci" + + - name: Enable end-to-end perf tests if: ${{ github.event.inputs.run-all }} run: echo "E2E_FLAGS='--all'" >> $GITHUB_ENV diff --git a/.vscode/settings.json b/.vscode/settings.json index 062f7f4..d210429 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -40,9 +40,9 @@ // Cucumber settings "cucumber.features": [ - "test/e2e/features/**/*.feature", + "test/**/features/**/*.feature", ], "cucumber.glue": [ - "test/e2e/features/**/*.ts" + "test/**/features/**/*.ts", ], } diff --git a/Makefile b/Makefile index 3bc3955..b1f653b 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,7 @@ APPLE_APP_IDENTITY = APPLE_INST_IDENTITY = APPLE_KEYCHAIN_PROFILE = E2E_FLAGS= +INTEGRATION_FLAGS= # Build targets .PHONY: build @@ -42,6 +43,18 @@ doc: --output="$(DOCDIR)" # Testing targets +.PHONY: test +test: build + @echo "======== Running unit tests ========" + GOOS="$(GOOS)" GOARCH="$(GOARCH)" go test ./... + +.PHONY: integration-test +integration-test: build + @echo + @echo "======== Running integration tests ========" + $(RM) -r $(TESTDIR) + @scripts/run-integration-tests.sh $(INTEGRATION_FLAGS) + .PHONY: e2e-test e2e-test: build @echo @@ -49,6 +62,9 @@ e2e-test: build $(RM) -r $(TESTDIR) @scripts/run-e2e-tests.sh $(E2E_FLAGS) +.PHONY: test-all +test-all: test integration-test e2e-test + # Installation targets .PHONY: install install: build doc diff --git a/scripts/run-e2e-tests.sh b/scripts/run-e2e-tests.sh index 6b85f84..be8ca78 100755 --- a/scripts/run-e2e-tests.sh +++ b/scripts/run-e2e-tests.sh @@ -30,4 +30,4 @@ set -e cd "$TESTDIR" npm install -npm run test -- ${ARGS:+${ARGS[*]}} +npm run e2e-test -- ${ARGS:+${ARGS[*]}} diff --git a/scripts/run-integration-tests.sh b/scripts/run-integration-tests.sh new file mode 100755 index 0000000..eb6af55 --- /dev/null +++ b/scripts/run-integration-tests.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +THISDIR="$( cd "$(dirname "$0")" ; pwd -P )" +TESTDIR="$THISDIR/../test/integration" + +# Defaults +ARGS=() + +# Parse script arguments +for i in "$@" +do +case "$i" in + --offline) + ARGS+=("-p" "offline") + shift # past argument + ;; + --ci) + ARGS+=("-p" "ci") + shift # past argument + ;; + *) + die "unknown option '$i'" + ;; +esac +done + +# Exit as soon as any line fails +set -e + +cd "$TESTDIR" + +npm install +npm run integration-test -- ${ARGS:+${ARGS[*]}} diff --git a/test/e2e/cucumber.js b/test/e2e/cucumber.js index 6f943c6..9d446c7 100644 --- a/test/e2e/cucumber.js +++ b/test/e2e/cucumber.js @@ -1,15 +1,15 @@ const common = { requireModule: ['ts-node/register'], - require: ['features/**/*.ts'], + require: [`${__dirname}/features/**/*.ts`, `${__dirname}/../shared/features/**/*.ts`], publishQuiet: true, format: ['progress'], formatOptions: { snippetInterface: 'async-await' }, worldParameters: { - bundleServerCommand: '../../bin/git-bundle-server', - bundleWebServerCommand: '../../bin/git-bundle-web-server', - trashDirectoryBase: '../../_test/e2e' + bundleServerCommand: 'bin/git-bundle-server', + bundleWebServerCommand: 'bin/git-bundle-web-server', + trashDirectoryBase: '_test/e2e' } } diff --git a/test/e2e/features/step_definitions/bundleServer.ts b/test/e2e/features/step_definitions/bundleServer.ts index dba6045..d84b651 100644 --- a/test/e2e/features/step_definitions/bundleServer.ts +++ b/test/e2e/features/step_definitions/bundleServer.ts @@ -1,18 +1,14 @@ -import * as utils from '../support/utils' -import { BundleServerWorld, } from '../support/world' +import * as utils from '../../../shared/support/utils' +import { EndToEndBundleServerWorld } from '../support/world' import { Given } from '@cucumber/cucumber' -Given('the bundle web server was started at port {int}', async function (this: BundleServerWorld, port: number) { - this.bundleServer.startWebServer(port) -}) - -Given('the bundle server has been initialized with the remote repo', async function (this: BundleServerWorld) { +Given('the bundle server has been initialized with the remote repo', async function (this: EndToEndBundleServerWorld) { if (this.remote === undefined) { throw new Error("Remote repository is not initialized") } - utils.assertStatus(0, this.bundleServer.init(this.remote)) + utils.assertStatus(0, this.bundleServer.init(this.remote, 'e2e')) }) -Given('the bundle server was updated for the remote repo', async function (this: BundleServerWorld) { +Given('the bundle server was updated for the remote repo', async function (this: EndToEndBundleServerWorld) { utils.assertStatus(0, this.bundleServer.update()) }) diff --git a/test/e2e/features/step_definitions/repository.ts b/test/e2e/features/step_definitions/repository.ts index fa45d56..89eb378 100644 --- a/test/e2e/features/step_definitions/repository.ts +++ b/test/e2e/features/step_definitions/repository.ts @@ -1,7 +1,7 @@ import * as assert from 'assert' -import * as utils from '../support/utils' +import * as utils from '../../../shared/support/utils' import { randomBytes } from 'crypto' -import { BundleServerWorld, User } from '../support/world' +import { EndToEndBundleServerWorld, User } from '../support/world' import { Given, When, Then } from '@cucumber/cucumber' /** @@ -10,7 +10,7 @@ import { Given, When, Then } from '@cucumber/cucumber' * test steps will live here. */ -Given('another user pushed {int} commits to {string}', async function (this: BundleServerWorld, commitNum: number, branch: string) { +Given('another user pushed {int} commits to {string}', async function (this: EndToEndBundleServerWorld, commitNum: number, branch: string) { const clonedRepo = this.getRepoAtBranch(User.Another, branch) for (let i = 0; i < commitNum; i++) { @@ -22,7 +22,7 @@ Given('another user pushed {int} commits to {string}', async function (this: Bun }) Given('another user removed {int} commits and added {int} commits to {string}', - async function (this: BundleServerWorld, removeCommits: number, addCommits: number, branch: string) { + async function (this: EndToEndBundleServerWorld, removeCommits: number, addCommits: number, branch: string) { const clonedRepo = this.getRepoAtBranch(User.Another, branch) // First, reset @@ -40,33 +40,33 @@ Given('another user removed {int} commits and added {int} commits to {string}', } ) -Given('I cloned from the remote repo with a bundle URI', async function (this: BundleServerWorld) { +Given('I cloned from the remote repo with a bundle URI', async function (this: EndToEndBundleServerWorld) { const user = User.Me this.cloneRepositoryFor(user, this.bundleServer.bundleUri()) utils.assertStatus(0, this.getRepo(user).cloneResult) }) -When('I clone from the remote repo with a bundle URI', async function (this: BundleServerWorld) { +When('I clone from the remote repo with a bundle URI', async function (this: EndToEndBundleServerWorld) { this.cloneRepositoryFor(User.Me, this.bundleServer.bundleUri()) }) -When('another developer clones from the remote repo without a bundle URI', async function (this: BundleServerWorld) { +When('another developer clones from the remote repo without a bundle URI', async function (this: EndToEndBundleServerWorld) { this.cloneRepositoryFor(User.Another) }) -When('I fetch from the remote', async function (this: BundleServerWorld) { +When('I fetch from the remote', async function (this: EndToEndBundleServerWorld) { const clonedRepo = this.getRepo(User.Me) utils.assertStatus(0, clonedRepo.runGit("fetch", "origin")) }) -Then('bundles are downloaded and used', async function (this: BundleServerWorld) { +Then('bundles are downloaded and used', async function (this: EndToEndBundleServerWorld) { const clonedRepo = this.getRepo(User.Me) // Verify the clone executed as-expected utils.assertStatus(0, clonedRepo.cloneResult, "git clone failed") // Ensure warning wasn't thrown - clonedRepo.cloneResult.stderr.toString().split("\n").forEach(function (line) { + clonedRepo.cloneResult.stderr.toString().split("\n").forEach(function (line: string) { if (line.startsWith("warning: failed to download bundle from URI")) { assert.fail(line) } @@ -81,13 +81,13 @@ Then('bundles are downloaded and used', async function (this: BundleServerWorld) result = clonedRepo.runGit("for-each-ref", "--format=%(refname)", "refs/bundles/*") utils.assertStatus(0, result, "git for-each-ref failed") - const bundleRefs = result.stdout.toString().split("\n").filter(function(line) { + const bundleRefs = result.stdout.toString().split("\n").filter(function(line: string) { return line.trim() != "" }) assert.strict(bundleRefs.length > 0, "No bundle refs found in the repo") }) -Then('I am up-to-date with {string}', async function (this: BundleServerWorld, branch: string) { +Then('I am up-to-date with {string}', async function (this: EndToEndBundleServerWorld, branch: string) { const clonedRepo = this.getRepo(User.Me) const result = clonedRepo.runGit("rev-parse", `refs/remotes/origin/${branch}`) utils.assertStatus(0, result) @@ -97,7 +97,7 @@ Then('I am up-to-date with {string}', async function (this: BundleServerWorld, b }) Then('my repo\'s bundles {boolean} up-to-date with {string}', - async function (this: BundleServerWorld, expectedUpToDate: boolean, branch: string) { + async function (this: EndToEndBundleServerWorld, expectedUpToDate: boolean, branch: string) { const clonedRepo = this.getRepo(User.Me) const result = clonedRepo.runGit("rev-parse", `refs/bundles/${branch}`) utils.assertStatus(0, result) @@ -112,7 +112,7 @@ Then('my repo\'s bundles {boolean} up-to-date with {string}', } ) -Then('I compare the clone execution times', async function (this: BundleServerWorld) { +Then('I compare the clone execution times', async function (this: EndToEndBundleServerWorld) { const myClone = this.getRepo(User.Me) const otherClone = this.getRepo(User.Another) diff --git a/test/e2e/features/support/utils.ts b/test/e2e/features/support/utils.ts deleted file mode 100644 index 5106786..0000000 --- a/test/e2e/features/support/utils.ts +++ /dev/null @@ -1,25 +0,0 @@ -import * as path from 'path' -import * as assert from 'assert' -import * as child_process from 'child_process' - -export function runGit(...args: string[]): child_process.SpawnSyncReturns { - return child_process.spawnSync("git", args) -} - -export function absPath(pathParam: string): string { - // Convert a given path (either relative to 'test/e2e/' or absolute) to an - // absolute path - if (!path.isAbsolute(pathParam)) { - return path.resolve(__dirname, "../..", pathParam) - } else { - return pathParam - } -} - -export function assertStatus(expectedStatusCode: number, result: child_process.SpawnSyncReturns, message?: string): void { - if (result.error) { - throw result.error - } - assert.strictEqual(result.status, expectedStatusCode, - `${message ?? "Invalid status code"}:\n\tstdout: ${result.stdout.toString()}\n\tstderr: ${result.stderr.toString()}`) -} diff --git a/test/e2e/features/support/world.ts b/test/e2e/features/support/world.ts index baa79a8..205bcdc 100644 --- a/test/e2e/features/support/world.ts +++ b/test/e2e/features/support/world.ts @@ -1,31 +1,14 @@ -import { setWorldConstructor, World, IWorldOptions } from '@cucumber/cucumber' -import { randomUUID } from 'crypto' -import { RemoteRepo } from '../classes/remote' -import * as utils from './utils' -import * as fs from 'fs' -import * as path from 'path' -import { ClonedRepository } from '../classes/repository' -import { BundleServer } from '../classes/bundleServer' +import { setWorldConstructor, IWorldOptions } from '@cucumber/cucumber' +import * as utils from '../../../shared/support/utils' +import { ClonedRepository } from '../../../shared/classes/repository' +import { BundleServerWorldBase, BundleServerParameters } from '../../../shared/support/world' export enum User { Me = 1, Another, } -interface BundleServerParameters { - bundleServerCommand: string - bundleWebServerCommand: string - trashDirectoryBase: string -} - -export class BundleServerWorld extends World { - // Internal variables - trashDirectory: string - - // Bundle server - bundleServer: BundleServer - remote: RemoteRepo | undefined - +export class EndToEndBundleServerWorld extends BundleServerWorldBase { // Users repoMap: Map @@ -33,14 +16,6 @@ export class BundleServerWorld extends World { super(options) this.repoMap = new Map() - - // Set up the trash directory - this.trashDirectory = path.join(utils.absPath(this.parameters.trashDirectoryBase), randomUUID()) - fs.mkdirSync(this.trashDirectory, { recursive: true }); - - // Set up the bundle server - this.bundleServer = new BundleServer(utils.absPath(this.parameters.bundleServerCommand), - utils.absPath(this.parameters.bundleWebServerCommand)) } cloneRepositoryFor(user: User, bundleUri?: string): void { @@ -84,13 +59,6 @@ export class BundleServerWorld extends World { return clonedRepo } - - cleanup(): void { - this.bundleServer.cleanup() - - // Delete the trash directory - fs.rmSync(this.trashDirectory, { recursive: true }) - } } -setWorldConstructor(BundleServerWorld) +setWorldConstructor(EndToEndBundleServerWorld) diff --git a/test/e2e/package-lock.json b/test/e2e/package-lock.json index 0825015..cdb553e 100644 --- a/test/e2e/package-lock.json +++ b/test/e2e/package-lock.json @@ -1,20 +1,8 @@ { - "name": "e2e-test", - "version": "1.0.0", + "name": "e2e", "lockfileVersion": 3, "requires": true, "packages": { - "": { - "name": "e2e-test", - "version": "1.0.0", - "license": "MIT", - "devDependencies": { - "@cucumber/cucumber": "^9.0.0", - "@types/node": "^18.14.6", - "ts-node": "^10.9.1", - "typescript": "^4.9.5" - } - }, "node_modules/@babel/runtime": { "version": "7.21.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", diff --git a/test/integration/cucumber.js b/test/integration/cucumber.js new file mode 100644 index 0000000..4c336ae --- /dev/null +++ b/test/integration/cucumber.js @@ -0,0 +1,28 @@ +const common = { + requireModule: ['ts-node/register'], + require: [`${__dirname}/features/**/*.ts`, `${__dirname}/../shared/features/**/*.ts`], + publishQuiet: true, + format: ['progress'], + formatOptions: { + snippetInterface: 'async-await' + }, + worldParameters: { + bundleServerCommand: `${__dirname}/../../bin/git-bundle-server`, + bundleWebServerCommand: `${__dirname}/../../bin/git-bundle-web-server`, + trashDirectoryBase: `${__dirname}/../../_test/integration` + } +} + +module.exports = { + default: { + ...common, + }, + offline: { + ...common, + tags: 'not @online', + }, + ci: { + ...common, + tags: 'not @daemon', + }, +} diff --git a/test/integration/features/command.feature b/test/integration/features/command.feature new file mode 100644 index 0000000..aec97b5 --- /dev/null +++ b/test/integration/features/command.feature @@ -0,0 +1,42 @@ +Feature: Bundle server command tests + + Background: The bundle web server is running + Given the bundle web server was started at port 8080 + + @online + Scenario: The init command initializes a bundle server repository + Given no bundle server repository exists at route 'integration/asset-hash' + When I run the bundle server CLI command 'init https://github.com/vdye/asset-hash.git integration/asset-hash' + Then a bundle server repository exists at route 'integration/asset-hash' + + @online + Scenario: The delete command removes route configuration and repository data + Given a remote repository 'https://github.com/vdye/asset-hash.git' + Given a bundle server repository is created at route 'integration/asset-hash' for the remote + When I run the bundle server CLI command 'delete integration/asset-hash' + Then the route configuration and repository data at 'integration/asset-hash' are removed + + Scenario: The update command fetches the latest remote content and updates the bundle list + Given no bundle server repository exists at route 'integration/bundle' + Given a new remote repository with main branch 'main' + Given the remote is cloned + Given 5 commits are pushed to the remote branch 'main' + Given a bundle server repository is created at route 'integration/bundle' for the remote + Given 2 commits are pushed to the remote branch 'main' + When I run the bundle server CLI command 'update integration/bundle' + Then the bundles are fetched and the bundle list is updated + + Scenario: The stop command updates the routes file + Given no bundle server repository exists at route 'integration/stop' + Given a new remote repository with main branch 'main' + Given a bundle server repository is created at route 'integration/stop' for the remote + When I run the bundle server CLI command 'stop integration/stop' + Then the route is removed from the routes file + + Scenario: The start command updates the routes file + Given no bundle server repository exists at route 'integration/start' + Given a new remote repository with main branch 'main' + Given a bundle server repository is created at route 'integration/start' for the remote + When I run the bundle server CLI command 'stop integration/start' + When I run the bundle server CLI command 'start integration/start' + Then the route exists in the routes file diff --git a/test/integration/features/daemon.feature b/test/integration/features/daemon.feature new file mode 100644 index 0000000..ecd87c6 --- /dev/null +++ b/test/integration/features/daemon.feature @@ -0,0 +1,11 @@ +@daemon +Feature: Bundle server daemon tests + Scenario: + Given the daemon has not been started + When I run the bundle server CLI command 'web-server start' + Then the daemon is running + + Scenario: + Given the daemon was started + When I run the bundle server CLI command 'web-server stop' + Then the daemon is not running diff --git a/test/integration/features/step_definitions/bundleServer.ts b/test/integration/features/step_definitions/bundleServer.ts new file mode 100644 index 0000000..da330a0 --- /dev/null +++ b/test/integration/features/step_definitions/bundleServer.ts @@ -0,0 +1,73 @@ +import * as assert from 'assert' +import { IntegrationBundleServerWorld } from '../support/world' +import { Given, Then } from '@cucumber/cucumber' +import * as utils from '../../../shared/support/utils' +import * as fs from 'fs' + +Given('a bundle server repository is created at route {string} for the remote', async function (this: IntegrationBundleServerWorld, route: string) { + if (!this.remote) { + throw new Error("Remote has not been initialized") + } + this.bundleServer.init(this.remote, 'integration', route) +}) + +Given('no bundle server repository exists at route {string}', async function (this: IntegrationBundleServerWorld, route: string) { + var repoPath = utils.repoRoot(route) + if (fs.existsSync(repoPath)) { + throw new Error(`Repo already exists at ${repoPath}`) + } +}) + +Then('a bundle server repository exists at route {string}', async function (this: IntegrationBundleServerWorld, route: string) { + var repoRoot = utils.repoRoot(route) + assert.equal(fs.existsSync(repoRoot), true) + assert.equal(fs.existsSync(`${repoRoot}/.git`), false) + assert.equal(fs.existsSync(`${repoRoot}/HEAD`), true) + assert.equal(fs.existsSync(`${repoRoot}/bundle-list.json`), true) + + // Set route for cleanup + this.bundleServer.route = route +}) + +Then('the route configuration and repository data at {string} are removed', async function (this: IntegrationBundleServerWorld, route: string) { + var repoRoot = utils.repoRoot(route) + var routeData = fs.readFileSync(utils.routesPath()) + + assert.equal(fs.existsSync(repoRoot), false) + assert.equal(routeData.includes(route), false) + + // Reset route to be ignored in cleanup + this.bundleServer.route = undefined +}) + +Then('the bundles are fetched and the bundle list is updated', async function (this: IntegrationBundleServerWorld) { + assert.strictEqual(this.commandResult?.stdout.toString() + .includes('Updating bundle list\n' + + 'Writing updated bundle list\n' + + 'Update complete'), true) + + if (this.bundleServer.initialBundleCount) { + const currentBundleCount = this.bundleServer.getBundleCount() + assert.strictEqual(currentBundleCount > this.bundleServer.initialBundleCount, true) + } else { + throw new Error("Bundle server not initialized") + } +}) + +Then('the route is removed from the routes file', async function (this: IntegrationBundleServerWorld) { + if (this.bundleServer.route) { + var routesPath = utils.routesPath() + var data = fs.readFileSync(routesPath); + assert.strictEqual(data.includes(this.bundleServer.route), false) + } +}) + +Then('the route exists in the routes file', async function (this: IntegrationBundleServerWorld) { + if (this.bundleServer.route) { + var routesPath = utils.routesPath() + var data = fs.readFileSync(routesPath); + assert.strictEqual(data.includes(this.bundleServer.route), true) + } else { + throw new Error("Route not set") + } +}) diff --git a/test/integration/features/step_definitions/command.ts b/test/integration/features/step_definitions/command.ts new file mode 100644 index 0000000..64c816d --- /dev/null +++ b/test/integration/features/step_definitions/command.ts @@ -0,0 +1,6 @@ +import { When } from "@cucumber/cucumber" +import { IntegrationBundleServerWorld } from '../support/world' + +When('I run the bundle server CLI command {string}', async function (this: IntegrationBundleServerWorld, command: string) { + this.runCommand(command) +}) diff --git a/test/integration/features/step_definitions/daemon.ts b/test/integration/features/step_definitions/daemon.ts new file mode 100644 index 0000000..e0389d7 --- /dev/null +++ b/test/integration/features/step_definitions/daemon.ts @@ -0,0 +1,35 @@ +import { After, Given, Then } from "@cucumber/cucumber" +import { IntegrationBundleServerWorld } from "../support/world" +import { DaemonState } from '../support/daemonState' +import * as assert from 'assert' + +Given('the daemon has not been started', async function (this: IntegrationBundleServerWorld) { + var daemonState = this.getDaemonState() + if (daemonState === DaemonState.Running) { + this.runCommand('git-bundle-server web-server stop') + } +}) + +Given('the daemon was started', async function (this: IntegrationBundleServerWorld) { + var daemonState = this.getDaemonState() + if (daemonState === DaemonState.NotRunning) { + this.runCommand('git-bundle-server web-server start') + } +}) + +Then('the daemon is running', async function (this: IntegrationBundleServerWorld) { + var daemonStatus = this.getDaemonState() + assert.strictEqual(daemonStatus, DaemonState.Running) +}) + +Then('the daemon is not running', async function (this: IntegrationBundleServerWorld) { + var daemonStatus = this.getDaemonState() + assert.strictEqual(daemonStatus, DaemonState.NotRunning) +}) + +// After({tags: '@daemon'}, async function (this: IntegrationBundleServerWorld) { +// var daemonState = this.getDaemonState() +// if (daemonState === DaemonState.Running) { +// this.runCommand('git-bundle-server web-server stop') +// } +// }); diff --git a/test/integration/features/step_definitions/remote.ts b/test/integration/features/step_definitions/remote.ts new file mode 100644 index 0000000..5d689e5 --- /dev/null +++ b/test/integration/features/step_definitions/remote.ts @@ -0,0 +1,26 @@ +import { Given } from '@cucumber/cucumber' +import { IntegrationBundleServerWorld } from '../support/world' +import * as utils from '../../../shared/support/utils' +import { randomBytes } from 'crypto' + +Given('the remote is cloned', async function (this: IntegrationBundleServerWorld) { + this.cloneRepository() +}) + +Given('{int} commits are pushed to the remote branch {string}', async function (this: IntegrationBundleServerWorld, commitNum: number, branch: string) { + if (this.local) { + for (let i = 0; i < commitNum; i++) { + utils.assertStatus(0, this.runShell(`echo ${randomBytes(16).toString('hex')} >${this.local.root}/README.md`)) + utils.assertStatus(0, utils.runGit("-C", this.local.root, "add", "README.md")) + utils.assertStatus(0, utils.runGit("-C", this.local.root, "commit", "-m", `test ${i + 1}`)) + } + } else { + throw new Error("Local repo not initialized") + } + + if (this.remote) { + utils.assertStatus(0, utils.runGit("-C", this.local.root, "push", "origin", branch)) + } else { + throw new Error("Remote repo not initialized") + } +}) diff --git a/test/integration/features/support/daemonState.ts b/test/integration/features/support/daemonState.ts new file mode 100644 index 0000000..de5f8c5 --- /dev/null +++ b/test/integration/features/support/daemonState.ts @@ -0,0 +1,58 @@ +import * as child_process from 'child_process' + +export enum DaemonState { + Running = 0, + NotRunning = 1 +} + +export function getLaunchDDaemonState(): DaemonState { + var regex = new RegExp(/^\s*state = (.*)$/, "m") + + var user = child_process.spawnSync('id', ['-u']).stdout.toString().trim() + var cmdResult = child_process.spawnSync('launchctl', + ['print', `user/${user}/com.github.gitbundleserver`]) + + var state = parseOutput(cmdResult.stdout.toString(), regex) + + switch(state) + { + case 'running': + return DaemonState.Running + case 'not running': + case 'not started': + return DaemonState.NotRunning + default: + throw new Error(`launchd daemon state ${state} not recognized`) + } +} + +export function getSystemDDaemonState(): DaemonState { + var regex = new RegExp(/^\s*Active: (.*)(?= \()/, "m") + + var user = child_process.spawnSync('id', ['-u']).stdout.toString() + var cmdResult = child_process.spawnSync('systemctl', + ['status', '--user', user, 'com.github.gitbundleserver']) + + var state = parseOutput(cmdResult.stdout.toString(), regex) + + switch(state) + { + case 'active': + return DaemonState.Running + case 'inactive': + case 'not started': + return DaemonState.NotRunning + default: + throw new Error(`systemd daemon state ${state} not recognized`) + } +} + +function parseOutput(stdout: string, regex: RegExp): string { + var potentialMatchParts = stdout.match(regex) + if (potentialMatchParts) { + return potentialMatchParts[1] + } + else { + return 'not started' + } +} diff --git a/test/integration/features/support/world.ts b/test/integration/features/support/world.ts new file mode 100644 index 0000000..2525eed --- /dev/null +++ b/test/integration/features/support/world.ts @@ -0,0 +1,39 @@ +import * as child_process from 'child_process' +import { RemoteRepo } from '../../../shared/classes/remote' +import { ClonedRepository } from '../../../shared/classes/repository' +import { BundleServerWorldBase } from '../../../shared/support/world' +import { setWorldConstructor } from '@cucumber/cucumber' +import { DaemonState, getLaunchDDaemonState, getSystemDDaemonState } from './daemonState' + +export class IntegrationBundleServerWorld extends BundleServerWorldBase { + remote: RemoteRepo | undefined + local: ClonedRepository | undefined + + commandResult: child_process.SpawnSyncReturns | undefined + + runCommand(commandArgs: string): void { + this.commandResult = child_process.spawnSync(`${this.parameters.bundleServerCommand} ${commandArgs}`, [], { shell: true }) + } + + runShell(command: string, ...args: string[]): child_process.SpawnSyncReturns { + return child_process.spawnSync(command, args, { shell: true }) + } + + cloneRepository(): void { + if (!this.remote) { + throw new Error("Remote repository is not initialized") + } + + const repoRoot = `${this.trashDirectory}/client` + this.local = new ClonedRepository(this.remote, repoRoot) + } + + getDaemonState(): DaemonState { + if (process.platform === "darwin") { + return getLaunchDDaemonState() + } + return getSystemDDaemonState() + } +} + +setWorldConstructor(IntegrationBundleServerWorld) diff --git a/test/package-lock.json b/test/package-lock.json new file mode 100644 index 0000000..6d48ceb --- /dev/null +++ b/test/package-lock.json @@ -0,0 +1,1285 @@ +{ + "name": "test", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "test", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "@cucumber/cucumber": "^9.0.0", + "@types/node": "^18.14.6", + "ts-node": "^10.9.1", + "typescript": "^4.9.5" + } + }, + "node_modules/@babel/runtime": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", + "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cucumber/ci-environment": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@cucumber/ci-environment/-/ci-environment-9.1.0.tgz", + "integrity": "sha512-jdnF6APXP3GawMue8kdMxhu6TBhyRUO4KDRxTowf06NtclLjIw2Ybpo9IcIOMvE8kHukvJyM00uxWX+CfS7JgQ==", + "dev": true + }, + "node_modules/@cucumber/cucumber": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@cucumber/cucumber/-/cucumber-9.0.0.tgz", + "integrity": "sha512-toJI6Y0PxshCKoKpSFZ+P5BURVui9UWnJx90x6cvlVkjdL6oFdb3Dg7rcErXJ27cs/sEXXYmzGFCRNwuKdqqzg==", + "dev": true, + "dependencies": { + "@cucumber/ci-environment": "9.1.0", + "@cucumber/cucumber-expressions": "16.1.1", + "@cucumber/gherkin": "26.0.3", + "@cucumber/gherkin-streams": "5.0.1", + "@cucumber/gherkin-utils": "8.0.2", + "@cucumber/html-formatter": "20.2.1", + "@cucumber/message-streams": "4.0.1", + "@cucumber/messages": "21.0.1", + "@cucumber/tag-expressions": "5.0.1", + "assertion-error-formatter": "^3.0.0", + "capital-case": "^1.0.4", + "chalk": "^4.1.2", + "cli-table3": "0.6.3", + "commander": "^9.0.0", + "debug": "^4.3.4", + "error-stack-parser": "^2.1.4", + "figures": "^3.2.0", + "glob": "^7.1.6", + "has-ansi": "^4.0.1", + "indent-string": "^4.0.0", + "is-installed-globally": "^0.4.0", + "is-stream": "^2.0.0", + "knuth-shuffle-seeded": "^1.0.6", + "lodash.merge": "^4.6.2", + "lodash.mergewith": "^4.6.2", + "luxon": "3.2.1", + "mz": "^2.7.0", + "progress": "^2.0.3", + "resolve-pkg": "^2.0.0", + "semver": "7.3.8", + "string-argv": "^0.3.1", + "strip-ansi": "6.0.1", + "supports-color": "^8.1.1", + "tmp": "^0.2.1", + "util-arity": "^1.1.0", + "verror": "^1.10.0", + "xmlbuilder": "^15.1.1", + "yaml": "1.10.2", + "yup": "^0.32.11" + }, + "bin": { + "cucumber-js": "bin/cucumber.js" + }, + "engines": { + "node": "14 || 16 || >=18" + } + }, + "node_modules/@cucumber/cucumber-expressions": { + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/@cucumber/cucumber-expressions/-/cucumber-expressions-16.1.1.tgz", + "integrity": "sha512-Ugsb9qxfgrgfUKsGvbx0awVk+69NIFjWfxNT+dnm62YrF2gdTHYxAOzOLuPgvE0yqYTh+3otrFLDDfkHGThM1g==", + "dev": true, + "dependencies": { + "regexp-match-indices": "1.0.2" + } + }, + "node_modules/@cucumber/gherkin": { + "version": "26.0.3", + "resolved": "https://registry.npmjs.org/@cucumber/gherkin/-/gherkin-26.0.3.tgz", + "integrity": "sha512-xwJHi//bLFEU1drIyw2yswwUHnnVWO4XcyVBbCTDs6DkSh262GkogFI/IWwChZqJfOXnPglzLGxR1DibcZsILA==", + "dev": true, + "dependencies": { + "@cucumber/messages": "19.1.4 - 21" + } + }, + "node_modules/@cucumber/gherkin-streams": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@cucumber/gherkin-streams/-/gherkin-streams-5.0.1.tgz", + "integrity": "sha512-/7VkIE/ASxIP/jd4Crlp4JHXqdNFxPGQokqWqsaCCiqBiu5qHoKMxcWNlp9njVL/n9yN4S08OmY3ZR8uC5x74Q==", + "dev": true, + "dependencies": { + "commander": "9.1.0", + "source-map-support": "0.5.21" + }, + "bin": { + "gherkin-javascript": "bin/gherkin" + }, + "peerDependencies": { + "@cucumber/gherkin": ">=22.0.0", + "@cucumber/message-streams": ">=4.0.0", + "@cucumber/messages": ">=17.1.1" + } + }, + "node_modules/@cucumber/gherkin-streams/node_modules/commander": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.1.0.tgz", + "integrity": "sha512-i0/MaqBtdbnJ4XQs4Pmyb+oFQl+q0lsAmokVUH92SlSw4fkeAcG3bVon+Qt7hmtF+u3Het6o4VgrcY3qAoEB6w==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/@cucumber/gherkin-utils": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@cucumber/gherkin-utils/-/gherkin-utils-8.0.2.tgz", + "integrity": "sha512-aQlziN3r3cTwprEDbLEcFoMRQajb9DTOu2OZZp5xkuNz6bjSTowSY90lHUD2pWT7jhEEckZRIREnk7MAwC2d1A==", + "dev": true, + "dependencies": { + "@cucumber/gherkin": "^25.0.0", + "@cucumber/messages": "^19.1.4", + "@teppeis/multimaps": "2.0.0", + "commander": "9.4.1", + "source-map-support": "^0.5.21" + }, + "bin": { + "gherkin-utils": "bin/gherkin-utils" + } + }, + "node_modules/@cucumber/gherkin-utils/node_modules/@cucumber/gherkin": { + "version": "25.0.2", + "resolved": "https://registry.npmjs.org/@cucumber/gherkin/-/gherkin-25.0.2.tgz", + "integrity": "sha512-EdsrR33Y5GjuOoe2Kq5Y9DYwgNRtUD32H4y2hCrT6+AWo7ibUQu7H+oiWTgfVhwbkHsZmksxHSxXz/AwqqyCRQ==", + "dev": true, + "dependencies": { + "@cucumber/messages": "^19.1.4" + } + }, + "node_modules/@cucumber/gherkin-utils/node_modules/@cucumber/messages": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@cucumber/messages/-/messages-19.1.4.tgz", + "integrity": "sha512-Pksl0pnDz2l1+L5Ug85NlG6LWrrklN9qkMxN5Mv+1XZ3T6u580dnE6mVaxjJRdcOq4tR17Pc0RqIDZMyVY1FlA==", + "dev": true, + "dependencies": { + "@types/uuid": "8.3.4", + "class-transformer": "0.5.1", + "reflect-metadata": "0.1.13", + "uuid": "9.0.0" + } + }, + "node_modules/@cucumber/gherkin-utils/node_modules/commander": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz", + "integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/@cucumber/html-formatter": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@cucumber/html-formatter/-/html-formatter-20.2.1.tgz", + "integrity": "sha512-bwwyr1WjlOJ5dEFOLGbtYWbUprloB2eymqXBmmTC10s0xapZXkFn4VfHgMshaH91XiCIY/MoabWNAau3AeMHkQ==", + "dev": true, + "peerDependencies": { + "@cucumber/messages": ">=18" + } + }, + "node_modules/@cucumber/message-streams": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@cucumber/message-streams/-/message-streams-4.0.1.tgz", + "integrity": "sha512-Kxap9uP5jD8tHUZVjTWgzxemi/0uOsbGjd4LBOSxcJoOCRbESFwemUzilJuzNTB8pcTQUh8D5oudUyxfkJOKmA==", + "dev": true, + "peerDependencies": { + "@cucumber/messages": ">=17.1.1" + } + }, + "node_modules/@cucumber/messages": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@cucumber/messages/-/messages-21.0.1.tgz", + "integrity": "sha512-pGR7iURM4SF9Qp1IIpNiVQ77J9kfxMkPOEbyy+zRmGABnWWCsqMpJdfHeh9Mb3VskemVw85++e15JT0PYdcR3g==", + "dev": true, + "dependencies": { + "@types/uuid": "8.3.4", + "class-transformer": "0.5.1", + "reflect-metadata": "0.1.13", + "uuid": "9.0.0" + } + }, + "node_modules/@cucumber/tag-expressions": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@cucumber/tag-expressions/-/tag-expressions-5.0.1.tgz", + "integrity": "sha512-N43uWud8ZXuVjza423T9ZCIJsaZhFekmakt7S9bvogTxqdVGbRobjR663s0+uW0Rz9e+Pa8I6jUuWtoBLQD2Mw==", + "dev": true + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@teppeis/multimaps": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@teppeis/multimaps/-/multimaps-2.0.0.tgz", + "integrity": "sha512-TL1adzq1HdxUf9WYduLcQ/DNGYiz71U31QRgbnr0Ef1cPyOUOsBojxHVWpFeOSUucB6Lrs0LxFRA14ntgtkc9w==", + "dev": true, + "engines": { + "node": ">=10.17" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, + "node_modules/@types/lodash": { + "version": "4.14.191", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz", + "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.14.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.6.tgz", + "integrity": "sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==", + "dev": true + }, + "node_modules/@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/assertion-error-formatter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/assertion-error-formatter/-/assertion-error-formatter-3.0.0.tgz", + "integrity": "sha512-6YyAVLrEze0kQ7CmJfUgrLHb+Y7XghmL2Ie7ijVa2Y9ynP3LV+VDiwFk62Dn0qtqbmY0BT0ss6p1xxpiF2PYbQ==", + "dev": true, + "dependencies": { + "diff": "^4.0.1", + "pad-right": "^0.2.2", + "repeat-string": "^1.6.1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/capital-case": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", + "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", + "dev": true + }, + "node_modules/cli-table3": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", + "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "dev": true, + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/extsprintf": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", + "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", + "dev": true, + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/global-dirs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "dev": true, + "dependencies": { + "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-ansi": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-4.0.1.tgz", + "integrity": "sha512-Qr4RtTm30xvEdqUXbSBVWDu+PrTokJOwe/FU+VdfJPk+MXAPoeOzKpRyrDTnZIJwAkQ4oBLTU53nu0HrkF/Z2A==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "dependencies": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/knuth-shuffle-seeded": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/knuth-shuffle-seeded/-/knuth-shuffle-seeded-1.0.6.tgz", + "integrity": "sha512-9pFH0SplrfyKyojCLxZfMcvkhf5hH0d+UwR9nTVJ/DDQJGuzcXjTwB7TP7sDfehSudlGGaOLblmEWqv04ERVWg==", + "dev": true, + "dependencies": { + "seed-random": "~2.2.0" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "dev": true + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/luxon": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.2.1.tgz", + "integrity": "sha512-QrwPArQCNLAKGO/C+ZIilgIuDnEnKx5QYODdDtbFaxzsbZcc/a7WFq7MhsVYgRlwawLtvOUESTlfJ+hc/USqPg==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoclone": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz", + "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==", + "dev": true + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/pad-right": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/pad-right/-/pad-right-0.2.2.tgz", + "integrity": "sha512-4cy8M95ioIGolCoMmm2cMntGR1lPLEbOMzOKu8bzjuJP6JpzEMQcDHmh7hHLYGgob+nKe1YHFMaG4V59HQa89g==", + "dev": true, + "dependencies": { + "repeat-string": "^1.5.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/property-expr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz", + "integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==", + "dev": true + }, + "node_modules/reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", + "dev": true + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true + }, + "node_modules/regexp-match-indices": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regexp-match-indices/-/regexp-match-indices-1.0.2.tgz", + "integrity": "sha512-DwZuAkt8NF5mKwGGER1EGh2PRqyvhRhhLviH+R8y8dIuaQROlUfXjt4s9ZTXstIsSkptf06BSvwcEmmfheJJWQ==", + "dev": true, + "dependencies": { + "regexp-tree": "^0.1.11" + } + }, + "node_modules/regexp-tree": { + "version": "0.1.24", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.24.tgz", + "integrity": "sha512-s2aEVuLhvnVJW6s/iPgEGK6R+/xngd2jNQ+xy4bXNDKxZKJH6jpPHY6kVeVv1IeLCHgswRj+Kl3ELaDjG6V1iw==", + "dev": true, + "bin": { + "regexp-tree": "bin/regexp-tree" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg/-/resolve-pkg-2.0.0.tgz", + "integrity": "sha512-+1lzwXehGCXSeryaISr6WujZzowloigEofRB+dj75y9RRa/obVcYgbHJd53tdYw8pvZj8GojXaaENws8Ktw/hQ==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/seed-random": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz", + "integrity": "sha512-34EQV6AAHQGhoc0tn/96a9Fsi6v2xdqe/dMUwljGRaFOzR3EgRmECvD0O8vi8X+/uQ50LGHfkNu/Eue5TPKZkQ==", + "dev": true + }, + "node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "dev": true + }, + "node_modules/string-argv": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", + "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", + "dev": true + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "dev": true + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/upper-case-first": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", + "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/util-arity": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/util-arity/-/util-arity-1.1.0.tgz", + "integrity": "sha512-kkyIsXKwemfSy8ZEoaIz06ApApnWsk5hQO0vLjZS6UkBiGiW++Jsyb8vSBoc0WKlffGoGs5yYy/j5pp8zckrFA==", + "dev": true + }, + "node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/verror": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", + "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yup": { + "version": "0.32.11", + "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz", + "integrity": "sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.15.4", + "@types/lodash": "^4.14.175", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "nanoclone": "^0.2.1", + "property-expr": "^2.0.4", + "toposort": "^2.0.2" + }, + "engines": { + "node": ">=10" + } + } + } +} diff --git a/test/e2e/package.json b/test/package.json similarity index 64% rename from test/e2e/package.json rename to test/package.json index c3e1267..2c47920 100644 --- a/test/e2e/package.json +++ b/test/package.json @@ -1,11 +1,12 @@ { - "name": "e2e-test", + "name": "test", "version": "1.0.0", "private": true, "description": "", "main": "index.js", "scripts": { - "test": "cucumber-js" + "e2e-test": "cucumber-js e2e -c e2e/cucumber.js", + "integration-test": "cucumber-js integration -c integration/cucumber.js" }, "license": "MIT", "devDependencies": { diff --git a/test/e2e/features/classes/bundleServer.ts b/test/shared/classes/bundleServer.ts similarity index 63% rename from test/e2e/features/classes/bundleServer.ts rename to test/shared/classes/bundleServer.ts index 457d747..90aa36b 100644 --- a/test/e2e/features/classes/bundleServer.ts +++ b/test/shared/classes/bundleServer.ts @@ -1,6 +1,8 @@ import { randomBytes } from 'crypto' import * as child_process from 'child_process' import { RemoteRepo } from './remote' +import * as fs from 'fs' +import * as utils from '../support/utils' export class BundleServer { private bundleServerCmd: string @@ -11,7 +13,8 @@ export class BundleServer { private bundleUriBase: string | undefined // Remote repo info (for now, only support one per test) - private route: string | undefined + route: string | undefined + initialBundleCount: number | undefined constructor(bundleServerCmd: string, bundleWebServerCmd: string) { this.bundleServerCmd = bundleServerCmd @@ -26,9 +29,21 @@ export class BundleServer { this.bundleUriBase = `http://localhost:${port}/` } - init(remote: RemoteRepo): child_process.SpawnSyncReturns { - this.route = `e2e/${randomBytes(8).toString('hex')}` - return child_process.spawnSync(this.bundleServerCmd, ["init", remote.remoteUri, this.route]) + init(remote: RemoteRepo, routePrefix: string, route: string = ""): child_process.SpawnSyncReturns { + if (route === "") { + route = `${routePrefix}/${randomBytes(8).toString('hex')}` + } + this.route = route + + const repoPath = utils.repoRoot(route) + if (fs.existsSync(repoPath)) { + throw new Error("Bundle server repository already exists") + } + + const result = child_process.spawnSync(this.bundleServerCmd, ["init", remote.remoteUri, this.route]) + this.initialBundleCount = this.getBundleCount() + + return result } update(): child_process.SpawnSyncReturns { @@ -49,6 +64,23 @@ export class BundleServer { return this.bundleUriBase + this.route } + getBundleCount(): number { + if (!this.route) { + throw new Error("Route is not defined") + } + + var matches: string[] = []; + const files = fs.readdirSync(`${utils.wwwPath()}/${this.route}`); + + for (const file of files) { + if (file.endsWith('.bundle')) { + matches.push(file); + } + } + + return matches.length; + } + cleanup(): void { if (this.webServerProcess) { const killed = this.webServerProcess.kill('SIGINT') diff --git a/test/e2e/features/classes/remote.ts b/test/shared/classes/remote.ts similarity index 100% rename from test/e2e/features/classes/remote.ts rename to test/shared/classes/remote.ts diff --git a/test/e2e/features/classes/repository.ts b/test/shared/classes/repository.ts similarity index 98% rename from test/e2e/features/classes/repository.ts rename to test/shared/classes/repository.ts index fbafa32..e7ca4e0 100644 --- a/test/e2e/features/classes/repository.ts +++ b/test/shared/classes/repository.ts @@ -4,9 +4,9 @@ import * as utils from '../support/utils' export class ClonedRepository { private initialized: boolean - private root: string private remote: RemoteRepo | undefined + root: string cloneResult: child_process.SpawnSyncReturns cloneTimeMs: number diff --git a/test/shared/features/step_definitions/bundleServer.ts b/test/shared/features/step_definitions/bundleServer.ts new file mode 100644 index 0000000..5d1cc26 --- /dev/null +++ b/test/shared/features/step_definitions/bundleServer.ts @@ -0,0 +1,6 @@ +import { Given } from '@cucumber/cucumber' +import { BundleServerWorldBase } from '../../support/world' + +Given('the bundle web server was started at port {int}', async function (this: BundleServerWorldBase, port: number) { + this.bundleServer.startWebServer(port) +}) diff --git a/test/e2e/features/step_definitions/remote.ts b/test/shared/features/step_definitions/remote.ts similarity index 67% rename from test/e2e/features/step_definitions/remote.ts rename to test/shared/features/step_definitions/remote.ts index 6eb827f..445bbac 100644 --- a/test/e2e/features/step_definitions/remote.ts +++ b/test/shared/features/step_definitions/remote.ts @@ -1,16 +1,16 @@ import { Given, } from '@cucumber/cucumber' -import { RemoteRepo } from '../classes/remote' -import { BundleServerWorld } from '../support/world' +import { RemoteRepo } from '../../classes/remote' +import { BundleServerWorldBase } from '../../support/world' import * as path from 'path' /** * Steps relating to the setup of the remote repository users will clone from. */ -Given('a remote repository {string}', async function (this: BundleServerWorld, url: string) { +Given('a remote repository {string}', async function (this: BundleServerWorldBase, url: string) { this.remote = new RemoteRepo(false, url) }) -Given('a new remote repository with main branch {string}', async function (this: BundleServerWorld, mainBranch: string) { +Given('a new remote repository with main branch {string}', async function (this: BundleServerWorldBase, mainBranch: string) { this.remote = new RemoteRepo(true, path.join(this.trashDirectory, "server"), mainBranch) }) diff --git a/test/e2e/features/step_definitions/shared.ts b/test/shared/features/step_definitions/shared.ts similarity index 55% rename from test/e2e/features/step_definitions/shared.ts rename to test/shared/features/step_definitions/shared.ts index 01e10e1..8c7bb4d 100644 --- a/test/e2e/features/step_definitions/shared.ts +++ b/test/shared/features/step_definitions/shared.ts @@ -1,10 +1,10 @@ -import { BundleServerWorld } from '../support/world' +import { BundleServerWorldBase } from '../../support/world' import { After } from '@cucumber/cucumber' /** * Steps handling operations that are common across tests. */ -After(function (this: BundleServerWorld) { +After(function (this: BundleServerWorldBase) { this.cleanup() }); diff --git a/test/shared/support/utils.ts b/test/shared/support/utils.ts new file mode 100644 index 0000000..82c79d8 --- /dev/null +++ b/test/shared/support/utils.ts @@ -0,0 +1,44 @@ +import * as assert from 'assert' +import * as child_process from 'child_process' +import * as path from 'path' + +const bundleRoot = `${process.env.HOME}/git-bundle-server` + +export function absPath(pathParam: string): string { + // Convert a given path (either relative to the top-level project directory or + // absolute) to an absolute path + if (!path.isAbsolute(pathParam)) { + return path.resolve(__dirname, "../../../", pathParam) + } else { + return pathParam + } +} + +export function runGit(...args: string[]): child_process.SpawnSyncReturns { + return child_process.spawnSync("git", args) +} + +export function assertStatus(expectedStatusCode: number, result: child_process.SpawnSyncReturns, message?: string): void { + if (result.error) { + console.log('error: ', result.error) + throw result.error + } + assert.strictEqual(result.status, expectedStatusCode, + `${message ?? "Invalid status code"}:\n\tstdout: ${result.stdout.toString()}\n\tstderr: ${result.stderr.toString()}`) +} + +export function wwwPath(): string { + return path.resolve(bundleRoot, "www") +} + +export function repoRoot(pathParam: string): string { + if (!path.isAbsolute(pathParam)) { + return path.resolve(bundleRoot, "git", pathParam) + } else { + return pathParam + } +} + +export function routesPath(): string { + return path.resolve(bundleRoot, "routes") +} diff --git a/test/shared/support/world.ts b/test/shared/support/world.ts new file mode 100644 index 0000000..48c7986 --- /dev/null +++ b/test/shared/support/world.ts @@ -0,0 +1,41 @@ +import { setWorldConstructor, World, IWorldOptions } from '@cucumber/cucumber' +import { randomUUID } from 'crypto' +import { RemoteRepo } from '../classes/remote' +import * as utils from './utils' +import * as fs from 'fs' +import * as path from 'path' +import { BundleServer } from '../classes/bundleServer' + +export interface BundleServerParameters { + bundleServerCommand: string + bundleWebServerCommand: string + trashDirectoryBase: string +} + +export class BundleServerWorldBase extends World { + // Internal variables + trashDirectory: string + + // Bundle server + bundleServer: BundleServer + remote: RemoteRepo | undefined + + constructor(options: IWorldOptions) { + super(options) + + // Set up the trash directory + this.trashDirectory = path.join(utils.absPath(this.parameters.trashDirectoryBase), randomUUID()) + fs.mkdirSync(this.trashDirectory, { recursive: true }); + + // Set up the bundle server + this.bundleServer = new BundleServer(utils.absPath(this.parameters.bundleServerCommand), + utils.absPath(this.parameters.bundleWebServerCommand)) + } + + cleanup(): void { + this.bundleServer.cleanup() + + // Delete the trash directory + fs.rmSync(this.trashDirectory, { recursive: true }) + } +} diff --git a/test/e2e/tsconfig.json b/test/tsconfig.json similarity index 100% rename from test/e2e/tsconfig.json rename to test/tsconfig.json