Skip to content

Commit

Permalink
API test part 2 (stream-labs#102)
Browse files Browse the repository at this point in the history
This PR includes:

- TypeScript support for tests
- unstable tests with application restart are fixed
- synchronous API calls support in tests
- errors from child window API requests now are sending to Raven
- better API errors handling

example how to run a single test:
`yarn test test-dist/test/scenes.js`
  • Loading branch information
holiber authored Dec 5, 2017
1 parent b2a25d9 commit a3c7406
Show file tree
Hide file tree
Showing 27 changed files with 754 additions and 416 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ rtmp-services/
text-freetype2/
data/
dist/
test-dist/
bundles/
node-obs/
node-boost/
67 changes: 60 additions & 7 deletions app/services-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ export class ServicesManager extends Service {
private mutationsBufferingEnabled = false;
private bufferedMutations: IMutation[] = [];

/**
* contains additional information about errors
* while JSONRPC request handling
*/
private requestErrors: string[] = [];

/**
* if result of calling a service method in the main window is promise -
* we create a linked promise in the child window and keep it callbacks here until
Expand Down Expand Up @@ -244,11 +250,29 @@ export class ServicesManager extends Service {

executeServiceRequest(request: IJsonRpcRequest): IJsonRpcResponse<any> {
let response: IJsonRpcResponse<any>;
this.requestErrors = [];

const handleErrors = (e?: any) => {
if (!e && this.requestErrors.length === 0) return;
if (e) {

// re-raise error for Raven
const isChildWindowRequest = request.params && request.params.fetchMutations;
if (isChildWindowRequest) setTimeout(() => { throw e; }, 0);
}

response = this.createErrorResponse({
code: E_JSON_RPC_ERROR.INTERNAL_SERVER_ERROR,
id: request.id,
message: this.requestErrors.join(';')
});
};

try {
response = this.handleServiceRequest(request);
handleErrors();
} catch (e) {
console.error(e);
response = this.createErrorResponse({ code: E_JSON_RPC_ERROR.INTERNAL_SERVER_ERROR, id: request.id });
handleErrors(e);
} finally {
return response;
}
Expand Down Expand Up @@ -307,7 +331,8 @@ export class ServicesManager extends Service {
const subscriptionId = `${resourceId}.${methodName}`;
responsePayload = {
_type: 'SUBSCRIPTION',
resourceId: subscriptionId
resourceId: subscriptionId,
emitter: 'STREAM',
};
if (!this.subscriptions[subscriptionId]) {
this.subscriptions[subscriptionId] = resource[methodName].subscribe((data: any) => {
Expand Down Expand Up @@ -344,10 +369,11 @@ export class ServicesManager extends Service {
id: request.id,
result: {
_type: 'SUBSCRIPTION',
resourceId: promiseId
resourceId: promiseId,
emitter: 'PROMISE',
}
};
} else if (responsePayload && responsePayload.isHelper) {
} else if (responsePayload && responsePayload.isHelper === true) {
const helper = responsePayload;

response = {
Expand All @@ -364,7 +390,7 @@ export class ServicesManager extends Service {
// payload can contain helpers-objects
// we have to wrap them in IpcProxy too
traverse(responsePayload).forEach((item: any) => {
if (item && item.isHelper) {
if (item && item.isHelper === true) {
const helper = this.getHelper(item.helperName, item.constructorArgs);
return {
_type: 'HELPER',
Expand Down Expand Up @@ -396,6 +422,10 @@ export class ServicesManager extends Service {
*/
private getResource(resourceId: string) {

if (resourceId === 'ServicesManager') {
return this;
}

if (this.services[resourceId]) {
return this.getInstance(resourceId) || this.initService(resourceId);
}
Expand All @@ -407,8 +437,31 @@ export class ServicesManager extends Service {
}


/**
* the information about resource scheme helps to improve performance for API clients
* this is undocumented feature is mainly for our API client that we're using in tests
*/
getResourceScheme(resourceId: string): Dictionary<string> {
const resource = this.getResource(resourceId);
if (!resource) {
this.requestErrors.push(`Resource not found: ${resourceId}`);
return null;
}
const resourceScheme = {};

Object.keys(Object.getPrototypeOf(resource)).concat(Object.keys(resource))
.forEach(key => {
resourceScheme[key] = typeof resource[key];
});

return resourceScheme;
}


private getHelperModel(helper: Object): Object {
if (helper['getModel']) return helper['getModel']();
if (helper['getModel'] && typeof helper['getModel'] === 'function') {
return helper['getModel']();
}
return {};
}

Expand Down
4 changes: 2 additions & 2 deletions app/services/scenes/scenes-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { ISourceApi, TSourceType, ISource } from '../sources';
* Api for scenes management
*/
export interface IScenesServiceApi {
createScene(name: string, options: ISceneCreateOptions): ISceneApi;
makeSceneActive(id: string): void;
createScene(name: string, options?: ISceneCreateOptions): ISceneApi;
makeSceneActive(id: string): boolean;
removeScene(id: string): IScene;
scenes: ISceneApi[];
activeScene: ISceneApi;
Expand Down
5 changes: 4 additions & 1 deletion app/services/scenes/scenes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,13 +157,16 @@ export class ScenesService extends StatefulService<IScenesState> implements ISce
}


makeSceneActive(id: string) {
makeSceneActive(id: string): boolean {
const scene = this.getScene(id);
if (!scene) return false;

const obsScene = scene.getObsScene();

this.transitionsService.transitionTo(obsScene);
this.MAKE_SCENE_ACTIVE(id);
this.sceneSwitched.next(scene.getModel());
return true;
}


Expand Down
64 changes: 51 additions & 13 deletions app/services/tcp-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ interface IClient {
id: number;
socket: WritableStream;
subscriptions: string[];

/**
* Clients with listenAllSubscriptions=true receive events that have been sent to other clients.
* This is helpful for tests.
*/
listenAllSubscriptions: boolean;
}

interface IServer {
Expand Down Expand Up @@ -267,7 +273,7 @@ export class TcpServerService extends PersistentStatefulService<ITcpServersSetti
this.log('new connection');

const id = this.nextClientId++;
const client: IClient = { id, socket, subscriptions: [] };
const client: IClient = { id, socket, subscriptions: [], listenAllSubscriptions: false };
this.clients[id] = client;

socket.on('data', (data: any) => {
Expand Down Expand Up @@ -304,24 +310,23 @@ export class TcpServerService extends PersistentStatefulService<ITcpServersSetti
return;
}

// handle unsubscribing by clearing client subscriptions
if (request.method === 'unsubscribe' && this.servicesManager.subscriptions[request.params.resource]) {
const subscriptionInd = client.subscriptions.indexOf(request.params.resource);
if (subscriptionInd !== -1) client.subscriptions.splice(subscriptionInd, 1);
this.sendResponse(client,{
jsonrpc: '2.0',
id: request.id,
result: subscriptionInd !== -1
});
return;
}
// some requests have to be handled by TcpServerService
if (this.hadleTcpServerDirectives(client, request)) return;

const response = this.servicesManager.executeServiceRequest(request);

// if response is subscription then add this subscription to client
if (response.result && response.result._type === 'SUBSCRIPTION') {
client.subscriptions.push(response.result.resourceId);
const subscriptionId = response.result.resourceId;
Object.keys(this.clients).forEach(clientId => {
const tcpClient = this.clients[clientId];
if (tcpClient.id !== client.id && !tcpClient.listenAllSubscriptions) return;
if (!tcpClient.subscriptions.includes(subscriptionId)) {
tcpClient.subscriptions.push(subscriptionId);
}
});
}

this.sendResponse(client, response);
} catch (e) {
this.sendResponse(
Expand Down Expand Up @@ -350,6 +355,39 @@ export class TcpServerService extends PersistentStatefulService<ITcpServersSetti
}


private hadleTcpServerDirectives(client: IClient, request: IJsonRpcRequest) {

// handle unsubscribing by clearing client subscriptions
if (
request.method === 'unsubscribe' &&
this.servicesManager.subscriptions[request.params.resource]
) {
const subscriptionInd = client.subscriptions.indexOf(request.params.resource);
if (subscriptionInd !== -1) client.subscriptions.splice(subscriptionInd, 1);
this.sendResponse(client,{
jsonrpc: '2.0',
id: request.id,
result: subscriptionInd !== -1
});
return true;
}

// handle `listenAllSubscriptions` directive
if (
request.method === 'listenAllSubscriptions' &&
request.params.resource === 'TcpServerService'
) {
client.listenAllSubscriptions = true;
this.sendResponse(client,{
jsonrpc: '2.0',
id: request.id,
result: true
});
return true;
}
}


private onDisconnectHandler(client: IClient) {
this.log('client disconnected');
delete this.clients[client.id];
Expand Down
6 changes: 4 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ Subscribe to the `sceneSwitched` event:
"id": 4,
"result": {
"_type": "SUBSCRIPTION",
"resourceId": "ScenesService.sceneSwitched"
"resourceId": "ScenesService.sceneSwitched",
"emitter": "STREAM"
}
}
```
Expand Down Expand Up @@ -195,7 +196,8 @@ execution.
"id": 5,
"result": {
"_type": "SUBSCRIPTION",
"resourceId": "5c3cf84f797a"
"resourceId": "5c3cf84f797a",
"emitter": "PROMISE"
}
}
```
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"install-plugins": "node_modules/7zip-bin-win/x64/7za.exe x plugins/obs-browser-2987-old.7z -onode-obs/",
"package": "rm -rf dist && build -w --x64 --em.env=production",
"release": "yarn install --cwd bin && node bin/release.js",
"test": "ava",
"test": "tsc -p test && ava",
"clear": "rm -rf bundles/media",
"typedoc": "typedoc --out docs/dist ./app/services --mode modules --theme ./docs/theme --readme ./docs/README.md --ignoreCompilerErrors --excludePrivate --excludeExternals --hideGenerator"
},
Expand Down Expand Up @@ -56,8 +56,8 @@
},
"ava": {
"files": [
"test/*.js",
"test/api/*.js"
"test-dist/test/*.js",
"test-dist/test/api/*.js"
],
"serial": true
},
Expand Down Expand Up @@ -86,6 +86,7 @@
"@types/lodash": "^4.14.64",
"@types/node": "^8.0.0",
"@types/socket.io-client": "^1.4.31",
"@types/webdriverio": "^4.8.6",
"archiver": "^2.0.3",
"ava": "^0.19.1",
"babel-core": "^6.22.1",
Expand Down
57 changes: 0 additions & 57 deletions test/api/audio.js

This file was deleted.

Loading

0 comments on commit a3c7406

Please sign in to comment.