Skip to content

Commit eb714f5

Browse files
committed
feat: mockservercontroller can spin up mockservers via http
1 parent 1ca0cd5 commit eb714f5

12 files changed

+325
-106
lines changed

.aegir.cjs

+13-12
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
require('ts-node').register({
55
project: 'tsconfig.json',
66
})
7-
const { MockServer } = require('./test/MockServer')
7+
8+
const { MockServerController } = require('./MockServerController')
9+
810

911
/** @type {import('aegir').PartialOptions} */
1012
module.exports = {
@@ -20,19 +22,18 @@ module.exports = {
2022
bundlesizeMax: '44KB'
2123
},
2224
test: {
25+
cov: false,
2326
async before () {
24-
25-
},
26-
async beforeEach () {
27-
28-
// mockServer = new MockServer()
29-
await mockServer.start()
30-
},
31-
async afterEach () {
32-
33-
await mockServer.stop()
27+
return new MockServerController()
3428
},
35-
async after () {
29+
/**
30+
*
31+
* @param {GlobalOptions & TestOptions} _
32+
* @param {MockServerController} controller
33+
*/
34+
async after (_, controller) {
35+
console.log('test after:')
36+
await controller.shutdown()
3637

3738
}
3839
}

test/MockServer.ts renamed to MockServer.ts

+26-15
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { setup } from 'mock-ipfs-pinning-service'
22
import type { Application } from 'express'
33
import type { Server } from 'http'
44
import portscanner from 'portscanner'
5+
import cors from 'cors'
6+
57
import('dotenvrc')
68

79
export class MockServer {
@@ -16,7 +18,7 @@ export class MockServer {
1618

1719
public basePath: string = ''
1820

19-
constructor () {
21+
constructor (private readonly config: Parameters<typeof import('mock-ipfs-pinning-service')['setup']>[0] = { token: process.env.MOCK_PINNING_SERVER_SECRET }) {
2022
process.on('uncaughtException', MockServer.onEADDRINUSE)
2123
}
2224

@@ -31,19 +33,19 @@ export class MockServer {
3133
if (server == null) {
3234
process.exit(1)
3335
}
34-
36+
const handler = this.cleanupSync.bind(this)
3537
// And you'll want to make sure you close the server when your process exits
36-
process.on('beforeExit', this.cleanupSync)
37-
process.on('SIGTERM', this.cleanupSync)
38-
process.on('SIGINT', this.cleanupSync)
39-
process.on('SIGHUP', this.cleanupSync)
38+
process.on('beforeExit', handler)
39+
process.on('SIGTERM', handler)
40+
process.on('SIGINT', handler)
41+
process.on('SIGHUP', handler)
4042

4143
// To prevent duplicated cleanup, remove the process listeners on server close.
4244
server.on('close', () => {
43-
process.off('beforeExit', this.cleanupSync)
44-
process.off('SIGTERM', this.cleanupSync)
45-
process.off('SIGINT', this.cleanupSync)
46-
process.off('SIGHUP', this.cleanupSync)
45+
process.off('beforeExit', handler)
46+
process.off('SIGTERM', handler)
47+
process.off('SIGINT', handler)
48+
process.off('SIGHUP', handler)
4749
})
4850
}
4951

@@ -59,6 +61,11 @@ export class MockServer {
5961
* Ensure the port set for this instance is not already in use by another MockServer
6062
*/
6163
private async getAvailablePort (): Promise<number> {
64+
// this.port = 3000
65+
66+
// this.setBasePath()
67+
68+
// return this.port
6269
return await new Promise((resolve, reject) => portscanner.findAPortNotInUse(3000, 3099, '127.0.0.1', (error, port) => {
6370
if (error != null) {
6471
return reject(error)
@@ -73,9 +80,12 @@ export class MockServer {
7380
if (this._service !== undefined) {
7481
return this._service
7582
}
76-
this._service = await setup({
77-
token: process.env.MOCK_PINNING_SERVER_SECRET
78-
})
83+
this._service = await setup(this.config)
84+
// this._service = await setup({
85+
// token: process.env.MOCK_PINNING_SERVER_SECRET,
86+
// loglevel: 'debug'
87+
// })
88+
this._service.use(cors())
7989

8090
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
8191
return this._service as Application
@@ -94,7 +104,7 @@ export class MockServer {
94104
MockServer.error('service error', err)
95105
})
96106
this._server = service.listen(await this.getAvailablePort(), () => {
97-
MockServer.debug(`server running on port ${port}`)
107+
MockServer.debug(`${Date.now()}: MockServer running on port ${this.port}`)
98108
})
99109

100110
return this._server
@@ -109,10 +119,11 @@ export class MockServer {
109119
// Express server cleanup handling.
110120
private async cleanup (): Promise<void> {
111121
const server = await this.server()
122+
const port = this.port
112123
server.close((err) => {
113124
if ((err == null) || (err as Error & { code: string })?.code === 'ERR_SERVER_NOT_RUNNING') {
114125
// MockServer.portsInUse.remove(this.port)
115-
MockServer.debug(`server stopped listening on port ${this.port}`)
126+
MockServer.debug(`${Date.now()}: MockServer stopped listening on port ${port}`)
116127
delete this._server
117128
}
118129
if (err != null) {

MockServerController.ts

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
#!/usr/bin/env ts-node
2+
3+
/* eslint-disable @typescript-eslint/no-misused-promises */
4+
/* eslint-disable no-console */
5+
import express from 'express'
6+
import { MockServer } from './MockServer'
7+
import Router from 'express-promise-router'
8+
import cors from 'cors'
9+
10+
// const startMockServerController = async () => {
11+
// /**
12+
// * @type {MockServer}
13+
// */
14+
// const mockServer = null
15+
16+
// app.get('/stop', (req, res) => {
17+
// res.send('Hello World!')
18+
// })
19+
20+
// app.listen(port, () => {
21+
// console.log(`Example app listening on port ${port}`)
22+
// })
23+
// }
24+
25+
// type ExpressGetHandler = Parameters<IRouterMatcher<'get'>>[1]
26+
27+
class MockServerController {
28+
private readonly mockServers: MockServer[] = []
29+
private readonly app = express()
30+
private readonly router = Router()
31+
32+
private readonly port = 3000
33+
server: import('http').Server
34+
constructor () {
35+
this.router.get<'/start', {port?: string}>('/start', async (req, res, next) => {
36+
const { port } = req.params
37+
38+
let mockServer: MockServer | null = null
39+
try {
40+
mockServer = await this.startIpfsPinningServer(port)
41+
this.mockServers.push(mockServer)
42+
43+
/**
44+
* We need to return the basePath and accessToken so the client can call the correct mockServer
45+
*/
46+
res.send({
47+
success: true,
48+
basePath: mockServer.basePath,
49+
accessToken: process.env.MOCK_PINNING_SERVER_SECRET
50+
})
51+
} catch (error) {
52+
res.json({ success: false, error })
53+
next(error)
54+
}
55+
})
56+
57+
/**
58+
* A client will request to shut down it's mockServer by port, which it should have received upon calling '/start'
59+
*/
60+
this.router.get<'/stop/:port', {port: string}>('/stop/:port', async (req, res, next) => {
61+
const { port } = req.params
62+
63+
const mockServer = this.mockServers.find((mockS) => mockS.basePath.includes(port))
64+
65+
if (mockServer != null) {
66+
try {
67+
await mockServer.stop()
68+
res.json({ success: true })
69+
} catch (error) {
70+
res.json({ success: false, error })
71+
next(error)
72+
}
73+
} else {
74+
console.log('Could not get mockserver')
75+
throw new Error(`MockServer at port ${port} could not be found`)
76+
}
77+
})
78+
79+
this.app.use(cors())
80+
this.app.use(this.router)
81+
82+
this.server = this.app.listen(this.port, () => {
83+
console.log(`MockServerController listening on port ${this.port}`)
84+
})
85+
86+
// And you'll want to make sure you close the server when your process exits
87+
process.on('beforeExit', this.shutdown)
88+
process.on('SIGTERM', this.shutdown)
89+
process.on('SIGINT', this.shutdown)
90+
process.on('SIGHUP', this.shutdown)
91+
92+
// To prevent duplicated cleanup, remove the process listeners on server close.
93+
this.server.on('close', () => {
94+
process.off('beforeExit', this.shutdown)
95+
process.off('SIGTERM', this.shutdown)
96+
process.off('SIGINT', this.shutdown)
97+
process.off('SIGHUP', this.shutdown)
98+
})
99+
}
100+
101+
async shutdown () {
102+
await new Promise<void>((resolve, reject) => {
103+
this.server.close((err) => {
104+
if (err != null) {
105+
console.error('Unexpected error when shutting down the MockServerController')
106+
console.error(err)
107+
} else {
108+
console.log(`MockServerController stopped listening on port ${this.port}`)
109+
}
110+
resolve()
111+
})
112+
})
113+
for await (const mockS of this.mockServers) {
114+
try {
115+
await mockS.stop()
116+
} catch (err) {
117+
console.error(`Unexpected error when attempting to shutdown mock server at ${mockS.basePath}`)
118+
console.error(err)
119+
}
120+
}
121+
}
122+
123+
// async startMockServer (req: {params: { port: number}}, res: {basePath: string, accessToken: string}, next: unknown) {
124+
125+
// }
126+
127+
private async startIpfsPinningServer (port?: string) {
128+
const mockServer = new MockServer({
129+
token: process.env.MOCK_PINNING_SERVER_SECRET
130+
// loglevel: 'info'
131+
})
132+
await mockServer.start(Number(port))
133+
134+
return mockServer
135+
}
136+
}
137+
138+
export { MockServerController }

package.json

+9-3
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@
144144
"build": "aegir build -- -p tsconfig.json --suppress 6133@generated 6192@generated --stats",
145145
"build:main": "aegir build -- -p tsconfig.json --suppress 6133@generated 6192@generated --stats",
146146
"build:docs": "aegir ts docs",
147-
"test": "run-p test:*",
147+
"test": "run-s test:*",
148148
"test:electron": "aegir test --target electron-main",
149149
"test:node": "aegir test --target node --cov && npx nyc report",
150150
"pregen": "openapi-generator-cli validate -i https://raw.githubusercontent.com/ipfs/pinning-services-api-spec/main/ipfs-pinning-service.yaml",
@@ -155,22 +155,28 @@
155155
"gen:ts": "openapi-generator-cli generate --generator-key ts"
156156
},
157157
"dependencies": {
158-
"node-fetch": "^3.2.0",
159158
"patch-package": "^6.4.7"
160159
},
161160
"devDependencies": {
162161
"@openapitools/openapi-generator-cli": "^2.4.26",
163162
"@swc/core": "^1.2.144",
164163
"@swc/helpers": "^0.3.3",
164+
"@types/cors": "^2.8.12",
165165
"@types/express": "^4.17.13",
166+
"@types/express-promise-router": "^3.0.0",
166167
"@types/mocha": "^9.1.0",
167-
"@types/node": "^17.0.16",
168+
"@types/node": "^17.0.21",
168169
"@types/node-fetch": "^3.0.3",
169170
"@types/portscanner": "^2.1.1",
170171
"aegir": "^36.1.3",
171172
"check-aegir-project": "^1.0.3",
173+
"cors": "^2.8.5",
172174
"dotenvrc": "^1.0.1",
175+
"express": "^4.17.3",
176+
"express-promise-router": "^4.1.1",
177+
"fetch-ponyfill": "^7.1.0",
173178
"mock-ipfs-pinning-service": "^0.4.0",
179+
"node-fetch": "^2.6.1",
174180
"npm-run-all": "^4.1.5",
175181
"openapi-typescript": "^5.1.1",
176182
"portscanner": "^2.2.0",

test/browser.ts

+6-8
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
/* eslint-disable no-console */
22

33
import clientTests from './isomorphic-tests/client'
4-
import configurationTests from './isomorphic-tests/configuration'
5-
6-
4+
// import configurationTests from './configuration.spec'
5+
import fetchPonyfill from 'fetch-ponyfill'
76

87
const setup = async () => {
9-
return await new Promise<void>((resolve, reject) => {
10-
void resolve()
11-
})
8+
return {
9+
fetch: fetchPonyfill().fetch as GlobalFetch['fetch']
10+
}
1211
}
1312

1413
// eslint-disable-next-line @typescript-eslint/no-misused-promises
1514
describe('browser', async (): Promise<void> => {
16-
console.log(fetch)
17-
await configurationTests(setup)
15+
// await configurationTests(setup)
1816
await clientTests(setup)
1917
})

test/configuration.spec.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/* eslint-env browser, node, mocha */
2+
3+
import { expect } from 'aegir/utils/chai'
4+
import { Configuration, ConfigurationParameters } from '../src'
5+
6+
// export default async (setup: () => Promise<unknown>) => {
7+
describe('Configuration', () => {
8+
it('Can be instantiated', () => {
9+
const configuration: ConfigurationParameters = {}
10+
expect(() => new Configuration(configuration)).not.to.throw()
11+
})
12+
})
13+
// }

0 commit comments

Comments
 (0)