Skip to content

Commit

Permalink
added spawn response test util
Browse files Browse the repository at this point in the history
  • Loading branch information
Danilo Romano committed Mar 18, 2022
1 parent 9e9fbe7 commit 2b1dd11
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 40 deletions.
16 changes: 12 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
FROM node:17-slim

ARG APP_NAME=spotify-radio
ARG HOME=/home/node

RUN apt-get update \
&& apt-get install sox libsox-fmt-mp3 -y
#libsox-fmt-all
USER node

RUN mkdir -p ${HOME}/${APP_NAME}

WORKDIR /spotify-radio
WORKDIR ${HOME}/${APP_NAME}

ENV NPM_CONFIG_PREFIX=$HOME/.npm-global
# allow npx executions like jest to work
ENV PATH=$PATH:$HOME/.npm-global/bin

COPY package*.json ./

RUN npm ci --silent

COPY . .

USER node
COPY --chown=node:node . .

CMD npm start
31 changes: 10 additions & 21 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,10 @@ services:
- npm
- start
volumes:
- .:/spotify-radio
- node_modules:/spotify-radio/node_modules
- .:/home/node/spotify-radio
- node_modules:/home/node/spotify-radio/node_modules
ports:
- 3000:3000
test-e2e:
image: spotify-radio
build: .
command:
- npm
- run
- test:e2e
volumes:
- .:/spotify-radio
- node_modules:/spotify-radio/node_modules
test:
image: spotify-radio
build: .
Expand All @@ -31,8 +21,8 @@ services:
- run
- test
volumes:
- .:/spotify-radio
- node_modules:/spotify-radio/node_modules
- .:/home/node/spotify-radio
- node_modules:/home/node/spotify-radio/node_modules
test-watch:
image: spotify-radio
build: .
Expand All @@ -41,20 +31,19 @@ services:
- run
- test:watch
volumes:
- .:/spotify-radio
- node_modules:/spotify-radio/node_modules
- .:/home/node/spotify-radio
- node_modules:/home/node/spotify-radio/node_modules
test-coverage:
image: spotify-radio
build: .
user: node
command:
- npm
- run
- test:coverage
volumes:
- .:/spotify-radio
- node_modules:/spotify-radio/node_modules
- .:/home/node/spotify-radio
- node_modules:/home/node/spotify-radio/node_modules

volumes:
node_modules: {}


node_modules: {}
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
"main": "index.js",
"scripts": {
"start": "nodemon",
"test": "LOG_DISABLED=true NODE_OPTIONS='--experimental-vm-modules --no-warnings' npx jest --no-cache",
"test:watch": "LOG_DISABLED=true NODE_OPTIONS='--experimental-vm-modules --no-warnings' npx jest --no-cache --watchAll",
"test:coverage": "LOG_DISABLED=true NODE_OPTIONS='--experimental-vm-modules --no-warnings' npx jest --no-cache --coverage tests/unit",
"test:e2e": "LOG_DISABLED=true NODE_OPTIONS='--experimental-vm-modules --no-warnings' npx jest --no-cache tests/e2e",
"test": "LOG_DISABLED=true NODE_OPTIONS='--experimental-vm-modules --no-warnings' npx jest --runInBand --no-cache",
"test:watch": "LOG_DISABLED=true NODE_OPTIONS='--experimental-vm-modules --no-warnings' npx jest --runInBand --no-cache --watchAll",
"test:coverage": "LOG_DISABLED=true NODE_OPTIONS='--experimental-vm-modules --no-warnings' npx jest --runInBand --no-cache --coverage tests/unit",
"test:e2e": "LOG_DISABLED=true NODE_OPTIONS='--experimental-vm-modules --no-warnings' npx jest --runInBand --no-cache tests/e2e",
"docker": "docker-compose up --build app",
"docker:test": "docker-compose up --build test",
"docker:test:watch": "docker-compose up --build test-watch",
Expand Down
2 changes: 1 addition & 1 deletion server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ import { createServer } from 'http'
import { handler } from './routes'


export default createServer(handler)
export default () => createServer(handler)
9 changes: 3 additions & 6 deletions server/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import config from './config'
import path from 'path'
import streamPromises from 'stream/promises'
import { randomUUID } from 'crypto'
import { PassThrough, Writable } from 'stream'
import { PassThrough, Readable, Writable } from 'stream'
import Throttle from 'throttle'
import childProcess from 'child_process'
import { logger } from './util'
Expand Down Expand Up @@ -85,8 +85,7 @@ export class Service {
once(stderr, "readable"),
once(stdout, "readable"),
])

const [success, error]: Buffer[] = [stdout, stderr].map(stream => stream.read())
const [success, error]: Readable[] = [stdout, stderr].map(stream => stream.read())
if (error) return await Promise.reject(error)

return success
Expand All @@ -95,9 +94,7 @@ export class Service {
.replace(/k/, "000")

} catch (error) {
if (error instanceof Error) {
logger.error(`Bitrate error:${error.message} - ${error.stack}`)
}
logger.error(`Bitrate error:${error}`)
return fallbackBitRate;
}
}
Expand Down
1 change: 1 addition & 0 deletions tests/e2e/server/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ describe("API e2e test suite", () => {
expect(onChunk).not.toHaveBeenCalled()

})

test('it should receive data stream if the process is playing', async () => {
const server = await getTestServer()
const onChunk = jest.fn()
Expand Down
10 changes: 7 additions & 3 deletions tests/unit/_util/testUtil.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { IncomingMessage, ServerResponse } from 'http'
import { Readable, Writable } from 'stream'
import { PassThrough, Readable, Writable } from 'stream'
import { jest } from '@jest/globals';
import { ReadStream } from 'fs';
export default class TestUtil {
Expand All @@ -15,10 +15,10 @@ export default class TestUtil {
}) as ReadStream
}

static generateWritableStream(onData = (...data: any[]) => { }) {
static generateWritableStream(onData?: (...data: any[]) => void) {
return new Writable({
write(chunk, enc, cb) {
onData(chunk)
onData?.(chunk)

cb(null)
}
Expand Down Expand Up @@ -47,4 +47,8 @@ export default class TestUtil {
...data
}
}

static generatePassThroughStream() {
return new PassThrough()
}
}
4 changes: 4 additions & 0 deletions tests/unit/server/controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,8 @@ describe("#Controller - test controller implementation", () => {

})

test.todo("#createClientStream")

test.todo("#handleCommand")

})
131 changes: 130 additions & 1 deletion tests/unit/server/service.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
import { jest } from '@jest/globals';
import childProcess, { ChildProcess } from 'child_process';
import { Console } from 'console';
import fs from 'fs';
import fsPromises from 'fs/promises';
import path from 'path';
import { PassThrough } from 'stream';
import config from "../../../server/config";
import { Service } from "../../../server/service";
import TestUtil from '../_util/testUtil';
import Throttle from 'throttle';

const { dir: { publicDirectory } } = config

jest.setTimeout(10000)
const { dir: { publicDirectory }, constants: { fallbackBitRate } } = config

describe("#Service - test service implementation", () => {
const getSpawnResponse = ({
stdout = '',
stderr = '',
stdin = () => {}
}) => ({
stdout: TestUtil.generateReadableStream([stdout]),
stderr: TestUtil.generateReadableStream([stderr]),
stdin: TestUtil.generateWritableStream(stdin),
})

beforeEach(() => {
jest.restoreAllMocks()
jest.clearAllMocks()
Expand Down Expand Up @@ -77,4 +93,117 @@ describe("#Service - test service implementation", () => {


})

test("#createClientStream", async () => {
const service = new Service()
const { clientStream, id } = service.createClientStream()

const serviceClients = service['clientStreams']

expect(serviceClients.get(id)).toStrictEqual(clientStream)

})

test("#removeClientStream", async () => {
const service = new Service()
const { id } = service.createClientStream()

const serviceClients = service['clientStreams']
service.removeClientStream(id)

expect(serviceClients.get(id)).toBeUndefined()

})

test("#executeSoxCommand", async () => {
jest.spyOn(childProcess, childProcess.spawn.name as "spawn").mockReturnValue({} as ChildProcess)
const service = new Service()
const commandArgs = ['arg1', 'arg2']
service['_executeSoxCommand'](commandArgs)

expect(childProcess.spawn).toHaveBeenLastCalledWith("sox", commandArgs)
})

test("#getBitRate success", async () => {
const readable = TestUtil.generateReadableStream(Buffer.from("127k"))


jest.spyOn(childProcess, "spawn").mockReturnValue({
stdout: readable,
stderr: readable,
stdin: null
} as unknown as ChildProcess)

const service = new Service()
const result = await service.getBitRate("song.mp3")
const expectedResult = "127000"
expect(result).toEqual(expectedResult)

})

test("#getBitRate with error fallback", async () => {
const song = "song.mp3"
const service = new Service()

const spawnResponse = getSpawnResponse({
stderr: 'error'
})

jest.spyOn(service, '_executeSoxCommand' as any).mockReturnValue(spawnResponse)


const promise = service.getBitRate(song)
const result = await promise;

expect(result).toEqual(fallbackBitRate)
expect(service['_executeSoxCommand']).toHaveBeenCalledWith(['--i', '-B', song])


})

test("#broadCast", async () => {
const service = new Service()
const { id, clientStream } = service.createClientStream()
jest.spyOn(clientStream, "write").mockReturnValue(true)

const broadCast = service.broadCast()
const chunk = Buffer.from('abc')

broadCast.write(chunk)
expect(clientStream.write).toHaveBeenCalledWith(chunk)
expect(service['clientStreams'].get(id)).toStrictEqual(clientStream)


})

test("#startStreaming", async () => {
const readable = TestUtil.generateReadableStream('123')
const writable = TestUtil.generateWritableStream()

const mockBitRate = "80"
const service = new Service()

jest.spyOn(service, service.getBitRate.name as "getBitRate").mockResolvedValue(mockBitRate)
jest.spyOn(service, service.broadCast.name as "broadCast").mockReturnValue(writable)
jest.spyOn(service, service.createFileStream.name as "createFileStream").mockReturnValue(readable)

expect(service['currentBitRate']).toEqual(0);
await service.startStreaming()
// default bitRateDivisor = 8
expect(service['currentBitRate']).toEqual(10);



})

test("#stopStreaming", async () => {
const service = new Service()
const throttleTransform = new Throttle(service['currentBitRate'])
service['throttleTransform'] = throttleTransform;
jest.spyOn(throttleTransform, "end").mockReturnThis()

service.stopStreaming()

expect(throttleTransform.end).toBeCalled()
})
})

0 comments on commit 2b1dd11

Please sign in to comment.