Skip to content

Commit 472f131

Browse files
committed
integration tests: commands
Add integration tests for the following commands, which represent core bundle server functionality: 1. init 2. delete 3. update 4. start 5. stop Signed-off-by: Lessley Dennington <[email protected]>
1 parent 2969732 commit 472f131

File tree

10 files changed

+274
-7
lines changed

10 files changed

+274
-7
lines changed

test/e2e/features/step_definitions/bundleServer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Given('the bundle server has been initialized with the remote repo', async funct
1010
if (this.remote === undefined) {
1111
throw new Error("Remote repository is not initialized")
1212
}
13-
shared_utils.assertStatus(0, this.bundleServer.init(this.remote))
13+
shared_utils.assertStatus(0, this.bundleServer.init(this.remote, 'e2e'))
1414
})
1515

1616
Given('the bundle server was updated for the remote repo', async function (this: BundleServerWorld) {
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
Feature: Bundle server command tests
2+
3+
Background: The bundle web server is running
4+
Given the bundle web server was started at port 8080
5+
6+
Scenario: The init command initializes a bundle server repository
7+
Given no bundle server repository exists at route 'integration/asset-hash'
8+
When I run the bundle server CLI command 'init https://github.com/vdye/asset-hash.git integration/asset-hash'
9+
Then a bundle server repository exists at route 'integration/asset-hash'
10+
11+
Scenario: The delete command removes route configuration and repository data
12+
Given a remote repository exists at url 'https://github.com/vdye/asset-hash.git'
13+
Given a bundle server repository is created at route 'integration/asset-hash' for the remote
14+
When I run the bundle server CLI command 'delete integration/asset-hash'
15+
Then the route configuration and repository data at 'integration/asset-hash' are removed
16+
17+
Scenario: The update command fetches the latest remote content and updates the bundle list
18+
Given no bundle server repository exists at route 'integration/bundle'
19+
Given a new remote repository with main branch 'main'
20+
Given the remote is cloned
21+
Given 5 commits are pushed to the remote branch 'main'
22+
Given a bundle server repository is created at route 'integration/bundle' for the remote
23+
Given 2 commits are pushed to the remote branch 'main'
24+
When I run the bundle server CLI command 'update integration/bundle'
25+
Then the bundles are fetched and the bundle list is updated
26+
27+
Scenario: The stop command updates the routes file
28+
Given no bundle server repository exists at route 'integration/stop'
29+
Given a new remote repository with main branch 'main'
30+
Given a bundle server repository is created at route 'integration/stop' for the remote
31+
When I run the bundle server CLI command 'stop integration/stop'
32+
Then the route is removed from the routes file
33+
34+
Scenario: The start command updates the routes file
35+
Given no bundle server repository exists at route 'integration/start'
36+
Given a new remote repository with main branch 'main'
37+
Given a bundle server repository is created at route 'integration/start' for the remote
38+
When I run the bundle server CLI command 'stop integration/start'
39+
When I run the bundle server CLI command 'start integration/start'
40+
Then the route exists in the routes file
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import * as assert from 'assert'
2+
import { BundleServerWorld, } from '../support/world'
3+
import { Given, Then } from '@cucumber/cucumber'
4+
import * as shared_utils from '../../../shared/support/utils'
5+
import * as fs from 'fs'
6+
7+
Given('the bundle web server was started at port {int}', async function (this: BundleServerWorld, port: number) {
8+
this.bundleServer.startWebServer(port)
9+
})
10+
11+
Given('a bundle server repository is created at route {string} for the remote', async function (this: BundleServerWorld, route: string) {
12+
if (!this.remote) {
13+
throw new Error("Remote has not been initialized")
14+
}
15+
this.bundleServer.init(this.remote, 'integration', route)
16+
})
17+
18+
Given('no bundle server repository exists at route {string}', async function (this: BundleServerWorld, route: string) {
19+
var repoPath = shared_utils.repoRoot(route)
20+
if (fs.existsSync(repoPath)) {
21+
throw new Error(`Repo already exists at ${repoPath}`)
22+
}
23+
})
24+
25+
Then('a bundle server repository exists at route {string}', async function (this: BundleServerWorld, route: string) {
26+
var repoRoot = shared_utils.repoRoot(route)
27+
assert.equal(fs.existsSync(repoRoot), true)
28+
assert.equal(fs.existsSync(`${repoRoot}/.git`), false)
29+
assert.equal(fs.existsSync(`${repoRoot}/bundle-list.json`), true)
30+
31+
// Set route for cleanup
32+
this.bundleServer.route = route
33+
})
34+
35+
Then('the route configuration and repository data at {string} are removed', async function (this: BundleServerWorld, route: string) {
36+
var repoRoot = shared_utils.repoRoot(route)
37+
var routeData = fs.readFileSync(shared_utils.routesPath())
38+
39+
assert.equal(fs.existsSync(repoRoot), false)
40+
assert.equal(routeData.includes(route), false)
41+
42+
// Reset route to be ignored in cleanup
43+
this.bundleServer.route = undefined
44+
})
45+
46+
Then('the bundles are fetched and the bundle list is updated', async function (this: BundleServerWorld) {
47+
assert.strictEqual(this.commandResult?.stdout.toString()
48+
.includes('Updating bundle list\n' +
49+
'Writing updated bundle list\n' +
50+
'Update complete'), true)
51+
52+
if (this.bundleServer.initialBundleCount) {
53+
const currentBundleCount = this.bundleServer.getBundleCount()
54+
assert.strictEqual(currentBundleCount > this.bundleServer.initialBundleCount, true)
55+
} else {
56+
throw new Error("Bundle server not initialized")
57+
}
58+
})
59+
60+
Then('the route is removed from the routes file', async function (this: BundleServerWorld) {
61+
if (this.bundleServer.route) {
62+
var routesPath = shared_utils.routesPath()
63+
var data = fs.readFileSync(routesPath, 'utf-8');
64+
assert.strictEqual(data.includes(this.bundleServer.route), false)
65+
}
66+
})
67+
68+
Then('the route exists in the routes file', async function (this: BundleServerWorld) {
69+
if (this.bundleServer.route) {
70+
var routesPath = shared_utils.routesPath()
71+
var data = fs.readFileSync(routesPath, 'utf-8');
72+
assert.strictEqual(data.includes(this.bundleServer.route), true)
73+
} else {
74+
throw new Error("Route not set")
75+
}
76+
})
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { When } from "@cucumber/cucumber"
2+
import { BundleServerWorld } from "../support/world"
3+
4+
When('I run the bundle server CLI command {string}', async function (this: BundleServerWorld, command: string) {
5+
this.runCommand(command)
6+
})
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Given } from "@cucumber/cucumber"
2+
import { RemoteRepo } from "../../../shared/classes/remote"
3+
import { BundleServerWorld } from "../support/world"
4+
import * as path from "path"
5+
import * as shared_utils from "../../../shared/support/utils"
6+
import { randomBytes } from "crypto"
7+
8+
Given('a remote repository exists at url {string}', async function (this: BundleServerWorld, url: string) {
9+
this.remote = new RemoteRepo(false, url)
10+
})
11+
Given('a new remote repository with main branch {string}', async function (this: BundleServerWorld, mainBranch: string) {
12+
this.remote = new RemoteRepo(true, path.join(this.trashDirectory, "server"), mainBranch)
13+
})
14+
15+
Given('the remote is cloned', async function (this: BundleServerWorld) {
16+
this.cloneRepository()
17+
})
18+
19+
Given('{int} commits are pushed to the remote branch {string}', async function (this: BundleServerWorld, commitNum: number, branch: string) {
20+
if (this.local) {
21+
for (let i = 0; i < commitNum; i++) {
22+
shared_utils.assertStatus(0, this.runShell(`echo ${randomBytes(16).toString('hex')} >${this.local.root}/README.md`))
23+
shared_utils.assertStatus(0, shared_utils.runGit("-C", this.local.root, "add", "README.md"))
24+
shared_utils.assertStatus(0, shared_utils.runGit("-C", this.local.root, "commit", "-m", `test ${i + 1}`))
25+
}
26+
} else {
27+
throw new Error("Local repo not initialized")
28+
}
29+
30+
if (this.remote) {
31+
shared_utils.assertStatus(0, shared_utils.runGit("-C", this.local.root, "push", "origin", branch))
32+
} else {
33+
throw new Error("Remote repo not initialized")
34+
}
35+
})
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { BundleServerWorld } from '../support/world'
2+
import { After } from '@cucumber/cucumber'
3+
4+
/**
5+
* Steps handling operations that are common across tests.
6+
*/
7+
8+
After(function (this: BundleServerWorld) {
9+
this.cleanup()
10+
});
Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,59 @@
11
import { setWorldConstructor, World, IWorldOptions } from '@cucumber/cucumber'
22
import * as child_process from 'child_process'
3+
import { RemoteRepo } from '../../../shared/classes/remote'
4+
import * as local_utils from './utils'
5+
import * as fs from 'fs'
6+
import { ClonedRepository } from '../../../shared/classes/repository'
7+
import {BundleServer} from '../../../shared/classes/bundleServer'
8+
import { randomUUID } from 'crypto'
9+
import * as path from 'path'
310

411
interface BundleServerParameters {
512
bundleServerCommand: string
13+
bundleWebServerCommand: string
14+
trashDirectoryBase: string
615
}
716

8-
export class BundleServerWorld extends World<BundleServerParameters> { }
17+
export class BundleServerWorld extends World<BundleServerParameters> {
18+
trashDirectory: string
19+
bundleServer: BundleServer
20+
21+
remote: RemoteRepo | undefined
22+
local: ClonedRepository | undefined
23+
24+
commandResult: child_process.SpawnSyncReturns<Buffer> | undefined
25+
26+
constructor(options: IWorldOptions<BundleServerParameters>) {
27+
super(options)
28+
29+
this.bundleServer = new BundleServer(this.parameters.bundleServerCommand,
30+
this.parameters.bundleWebServerCommand)
31+
32+
// Set up the trash directory
33+
this.trashDirectory = path.join(local_utils.absPath(this.parameters.trashDirectoryBase), randomUUID())
34+
fs.mkdirSync(this.trashDirectory, { recursive: true });
35+
}
36+
37+
runCommand(commandArgs: string): void {
38+
this.commandResult = child_process.spawnSync(`${this.parameters.bundleServerCommand} ${commandArgs}`, [], { shell: true })
39+
}
40+
41+
runShell(command: string, ...args: string[]): child_process.SpawnSyncReturns<Buffer> {
42+
return child_process.spawnSync(command, args, { shell: true })
43+
}
44+
45+
cloneRepository(): void {
46+
if (!this.remote) {
47+
throw new Error("Remote repository is not initialized")
48+
}
49+
50+
const repoRoot = `${this.trashDirectory}/client`
51+
this.local = new ClonedRepository(this.remote, repoRoot)
52+
}
53+
54+
cleanup(): void {
55+
this.bundleServer.cleanup()
56+
}
57+
}
958

1059
setWorldConstructor(BundleServerWorld)

test/shared/classes/bundleServer.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { randomBytes } from 'crypto'
22
import * as child_process from 'child_process'
33
import { RemoteRepo } from './remote'
4+
import * as fs from 'fs'
5+
import * as utils from '../support/utils'
46

57
export class BundleServer {
68
private bundleServerCmd: string
@@ -11,7 +13,8 @@ export class BundleServer {
1113
private bundleUriBase: string | undefined
1214

1315
// Remote repo info (for now, only support one per test)
14-
private route: string | undefined
16+
route: string | undefined
17+
initialBundleCount: number | undefined
1518

1619
constructor(bundleServerCmd: string, bundleWebServerCmd: string) {
1720
this.bundleServerCmd = bundleServerCmd
@@ -26,9 +29,21 @@ export class BundleServer {
2629
this.bundleUriBase = `http://localhost:${port}/`
2730
}
2831

29-
init(remote: RemoteRepo): child_process.SpawnSyncReturns<Buffer> {
30-
this.route = `e2e/${randomBytes(8).toString('hex')}`
31-
return child_process.spawnSync(this.bundleServerCmd, ["init", remote.remoteUri, this.route])
32+
init(remote: RemoteRepo, routePrefix: string, route: string = ""): child_process.SpawnSyncReturns<Buffer> {
33+
if (route === "") {
34+
route = `${routePrefix}/${randomBytes(8).toString('hex')}`
35+
}
36+
this.route = route
37+
38+
const repoPath = utils.repoRoot(route)
39+
if (fs.existsSync(repoPath)) {
40+
throw new Error("Bundle server repository already exists")
41+
}
42+
43+
const result = child_process.spawnSync(this.bundleServerCmd, ["init", remote.remoteUri, this.route])
44+
this.initialBundleCount = this.getBundleCount()
45+
46+
return result
3247
}
3348

3449
update(): child_process.SpawnSyncReturns<Buffer> {
@@ -49,6 +64,23 @@ export class BundleServer {
4964
return this.bundleUriBase + this.route
5065
}
5166

67+
getBundleCount(): number {
68+
if (!this.route) {
69+
throw new Error("Route is not defined")
70+
}
71+
72+
var matches: string[] = [];
73+
const files = fs.readdirSync(`${utils.wwwPath()}/${this.route}`);
74+
75+
for (const file of files) {
76+
if (file.endsWith('.bundle')) {
77+
matches.push(file);
78+
}
79+
}
80+
81+
return matches.length;
82+
}
83+
5284
cleanup(): void {
5385
if (this.webServerProcess) {
5486
const killed = this.webServerProcess.kill('SIGINT')

test/shared/classes/repository.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import * as shared_utils from '../support/utils'
44

55
export class ClonedRepository {
66
private initialized: boolean
7-
private root: string
87
private remote: RemoteRepo | undefined
98

9+
root: string
1010
cloneResult: child_process.SpawnSyncReturns<Buffer>
1111
cloneTimeMs: number
1212

test/shared/support/utils.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import * as assert from 'assert'
22
import * as child_process from 'child_process'
3+
import * as path from 'path'
4+
5+
const bundleRoot = `${process.env.HOME}/git-bundle-server`
36

47
export function runGit(...args: string[]): child_process.SpawnSyncReturns<Buffer> {
58
return child_process.spawnSync("git", args)
@@ -12,3 +15,19 @@ export function assertStatus(expectedStatusCode: number, result: child_process.S
1215
assert.strictEqual(result.status, expectedStatusCode,
1316
`${message ?? "Invalid status code"}:\n\tstdout: ${result.stdout.toString()}\n\tstderr: ${result.stderr.toString()}`)
1417
}
18+
19+
export function wwwPath(): string {
20+
return path.resolve(bundleRoot, "www")
21+
}
22+
23+
export function repoRoot(pathParam: string): string {
24+
if (!path.isAbsolute(pathParam)) {
25+
return path.resolve(bundleRoot, "git", pathParam)
26+
} else {
27+
return pathParam
28+
}
29+
}
30+
31+
export function routesPath(): string {
32+
return path.resolve(bundleRoot, "routes")
33+
}

0 commit comments

Comments
 (0)