Skip to content

Commit

Permalink
feat: drill-test-id
Browse files Browse the repository at this point in the history
  • Loading branch information
RomanDavlyatshin committed Feb 2, 2022
1 parent 8f45404 commit 8e36b3a
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 69 deletions.
75 changes: 49 additions & 26 deletions src/background/backend-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,34 @@ import axios, { AxiosError } from 'axios';
import { browser } from 'webextension-polyfill-ts';
import { DrillSocket } from '../common/connection/drill-socket';
import { SessionActionError } from '../common/errors/session-action-error';
import { TestInfo } from './types';

const AUTH_TOKEN_HEADER_NAME = 'Authorization';

export default async (backendUrl: string, errorCb: any, completeCb: any) => {
const token = await setupAxios(backendUrl);
const adminSocket = createAdminSocket(backendUrl, token, () => {
adminSocket.cleanup();
test2CodeSocket.cleanup();
errorCb();
}, completeCb);
const test2CodeSocket = createTest2CodeSocket(backendUrl, token, () => {}, () => {}); // FIXME test2code - individual connection handling
const adminSocket = createAdminSocket(
backendUrl,
token,
() => {
adminSocket.cleanup();
test2CodeSocket.cleanup();
errorCb();
},
completeCb,
);
const test2CodeSocket = createTest2CodeSocket(
backendUrl,
token,
() => {},
() => {},
); // FIXME test2code - individual connection handling
let test2CodeSubs: Record<string, any> = {};

return {
subscribeAdmin(route: string, handler: any) {
// TODO check for duplicate subscriptions
const unsubscribe = adminSocket.subscribe(
route,
handler,
);
const unsubscribe = adminSocket.subscribe(route, handler);
return unsubscribe;
},
getTest2CodeSubs() {
Expand All @@ -39,8 +47,7 @@ export default async (backendUrl: string, errorCb: any, completeCb: any) => {

const subscriptionAlreadyExists = test2CodeSubs[`${agentId}${buildVersion}`][route];
if (subscriptionAlreadyExists) {
console.log('DUPLICATE SUBSCRIPTION',
'agentId', agentId, 'buildVersion', buildVersion, 'route', route);
console.log('DUPLICATE SUBSCRIPTION', 'agentId', agentId, 'buildVersion', buildVersion, 'route', route);
throw new Error('DUPLICATE_SUBSCRIPTION');
}

Expand All @@ -56,9 +63,18 @@ export default async (backendUrl: string, errorCb: any, completeCb: any) => {

const toHandler = test2CodeSubs[`${to.agentId}${to.buildVersion}`][route];
if (!toHandler) {
console.log('WARNING: missing handler for test2code update',
'\n\t', 'route', route, 'agentId', agentId, 'buildVersion', buildVersion,
'\n\t', 'unsubscribing');
console.log(
'WARNING: missing handler for test2code update',
'\n\t',
'route',
route,
'agentId',
agentId,
'buildVersion',
buildVersion,
'\n\t',
'unsubscribing',
);
unsubscribe();
}
toHandler(data);
Expand All @@ -73,28 +89,27 @@ export default async (backendUrl: string, errorCb: any, completeCb: any) => {

getMethods(baseUrl: string) {
return {
async startTest(testName: string, isRealtime: boolean) {
async startSession(isRealtime: boolean) {
const sessionId = uuid();
await sendSessionAction(baseUrl, {
type: 'START',
payload: {
sessionId,
testName,
testType: 'MANUAL',
isRealtime,
},
});
return sessionId;
},

async stopTest(sessionId: string) {
async stopSession(sessionId: string) {
await sendSessionAction(baseUrl, {
type: 'STOP',
payload: { sessionId },
});
},

async cancelTest(sessionId: string) {
async cancelSession(sessionId: string) {
await sendSessionAction(baseUrl, {
type: 'CANCEL',
payload: { sessionId },
Expand All @@ -110,6 +125,16 @@ export default async (backendUrl: string, errorCb: any, completeCb: any) => {
},
});
},

async addTests(sessionId: string, tests: TestInfo[]): Promise<void> {
await sendSessionAction(baseUrl, {
type: 'ADD_TESTS',
payload: {
sessionId,
tests,
},
});
},
};
},
};
Expand Down Expand Up @@ -140,13 +165,11 @@ async function setupAxios(backendUrl: string) {

const authToken = await getAuthToken(); // TODO move that out of setupAxios method

axios.interceptors.request.use(
async (config) => {
// eslint-disable-next-line no-param-reassign
config.headers[AUTH_TOKEN_HEADER_NAME] = `Bearer ${authToken}`;
return config;
},
);
axios.interceptors.request.use(async (config) => {
// eslint-disable-next-line no-param-reassign
config.headers[AUTH_TOKEN_HEADER_NAME] = `Bearer ${authToken}`;
return config;
});

axios.interceptors.response.use(
undefined, // (response) => response,
Expand Down
94 changes: 59 additions & 35 deletions src/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,24 @@ import initBackendApi from './backend-api';
import jsCoverageRecorder from './js-coverage-recorder';
import { transformHost } from '../common/util/transform-host';
import * as localStorageUtil from '../common/util/local-storage';
import type {
import {
AdapterInfo,
AgentAdapter,
BackendApi,
BackendCreator,
ScopeData,
SessionData,
SubNotifyFunction,
TestInfo,
TestResult,
} from './types';
import { SessionStatus, AgentType, BackendConnectionStatus } from '../common/enums';
import { setupResponseInterceptor } from './response-interceptor';
import { repeatAsync } from '../common/util/repeat-async';
import { setupRequestInterceptor } from './request-interceptor';
import { objectPropsToArray } from '../common/util/object-props-to-array';
import { SessionActionError } from '../common/errors/session-action-error';
import { getHash } from './util';

init();
async function init() {
Expand Down Expand Up @@ -106,10 +109,7 @@ async function init() {
function updateAgents(data: any) {
// TODO refactor to store + actions + reducers
rawAgentData = data;
const newInfo = [
...agentAdaptersReducer(data, interceptedDataStore?.agents),
...sgAdaptersReducer(data, interceptedDataStore?.agents),
];
const newInfo = [...agentAdaptersReducer(data, interceptedDataStore?.agents), ...sgAdaptersReducer(data, interceptedDataStore?.agents)];
agentsData = newInfo.reduce((a, z) => ({ ...a, [z.host]: z }), {});
agentsDataById = newInfo.reduce((a, z) => ({ ...a, [z.id]: z }), {});

Expand All @@ -127,9 +127,7 @@ async function init() {
if (!portId) throw new Error(`Can't assign port id for ${port}`);
const senderHost = transformHost(port.sender?.url);
const portMessageHandler = (message: any) => {
const {
type, resource, options,
} = message;
const { type, resource, options } = message;
const host = transformHost(options) || senderHost;
console.log('MESSAGE from', portId, host, 'with', message);

Expand Down Expand Up @@ -230,8 +228,9 @@ async function init() {
const host = transformHost(sender.url);
const adapter = adapters[host];
if (!adapter) throw new Error('Backend connection unavailable');
const sessionId = await adapter.startTest(testName, isRealtime, sender);
const sessionId = await adapter.startSession(isRealtime, sender);
sessionsData[host] = {
testId: getHash(testName),
testName,
sessionId,
start: Date.now(),
Expand All @@ -246,12 +245,30 @@ async function init() {
if (!adapter) throw new Error('Backend connection unavailable');
// TODO !sessionsData[host] check?
try {
await adapter.stopTest(sessionsData[host].sessionId, sessionsData[host].testName, sender);
// FIXME sessionsData[host] juggling
// adding "end" to sessionData might seem unnecessary
// but will come in handy later, for re-submitting test result implementation
sessionsData[host] = {
...sessionsData[host],
status: SessionStatus.STOPPED,
end: Date.now(),
};

const testInfo: TestInfo = {
id: sessionsData[host].testId,
startedAt: sessionsData[host].start,
finishedAt: Number(sessionsData[host].end),
result: 'PASSED' as TestResult,
details: {
testName: sessionsData[host].testName,
},
};

await adapter.addTests(sessionsData[host].sessionId, [testInfo]);
await adapter.stopSession(sessionsData[host].sessionId, sessionsData[host].testName, sender);
sessionsData[host] = {
...sessionsData[host],
status: SessionStatus.STOPPED,
};
} catch (e) {
// TODO conditional throwing/not-throwing is kinda unintuitive
if (e instanceof SessionActionError) {
Expand All @@ -276,7 +293,7 @@ async function init() {
if (!adapter) throw new Error('Backend connection unavailable');
// TODO !sessionsData[host] check?
try {
await adapter.cancelTest(sessionsData[host].sessionId, sender);
await adapter.cancelSession(sessionsData[host].sessionId, sender);
sessionsData[host] = {
...sessionsData[host],
status: SessionStatus.CANCELED,
Expand Down Expand Up @@ -330,14 +347,15 @@ async function init() {

async function connect(connectCb: any, disconnectCb: any) {
// TODO add timeout and a way to restart
const repeatUntilConnect = () => repeatAsync(async () => {
const connection = await connectToBackend(() => {
const reconnectPromise = repeatUntilConnect();
disconnectCb(reconnectPromise);
});
connectCb(connection);
return connection;
}, true);
const repeatUntilConnect = () =>
repeatAsync(async () => {
const connection = await connectToBackend(() => {
const reconnectPromise = repeatUntilConnect();
disconnectCb(reconnectPromise);
});
connectCb(connection);
return connection;
}, true);
return repeatUntilConnect();
}

Expand All @@ -361,7 +379,7 @@ async function connectToBackend(disconnectCb: any): Promise<BackendCreator | nul
}

function notifyAllSubs(subsPerHost: Record<string, Record<string, SubNotifyFunction>>, data: BackendConnectionStatus) {
objectPropsToArray(subsPerHost).forEach(x => notifySubscribers(x, data));
objectPropsToArray(subsPerHost).forEach((x) => notifySubscribers(x, data));
}

function setupResponseInterceptors(interceptedDataStore: Record<string, any>) {
Expand Down Expand Up @@ -439,7 +457,7 @@ function createAdaptersFromInfo(data: AdapterInfo[], backend: BackendCreator) {
}

function notifySubscribers(subscribers: Record<string, SubNotifyFunction>, data: unknown) {
objectPropsToArray<SubNotifyFunction>(subscribers).forEach(notify => notify(data));
objectPropsToArray<SubNotifyFunction>(subscribers).forEach((notify) => notify(data));
}

function createPortUpdater(port: chrome.runtime.Port, resource: string): SubNotifyFunction {
Expand All @@ -455,32 +473,38 @@ function createAdapter(adapterInfo: AdapterInfo, backend: BackendCreator): Agent

if (!adapterInfo.mustRecordJsCoverage) {
return {
startTest: async (testName, isRealtime) => {
const sessionId = await backendApi.startTest(testName, isRealtime);
startSession: async (isRealtime) => {
const sessionId = await backendApi.startSession(isRealtime);
return sessionId;
},
stopTest: async (sessionId: string) => {
await backendApi.stopTest(sessionId);
addTests: async (sessionId, tests) => {
await backendApi.addTests(sessionId, tests);
},
cancelTest: async (sessionId: string) => {
await backendApi.cancelTest(sessionId);
stopSession: async (sessionId: string) => {
await backendApi.stopSession(sessionId);
},
cancelSession: async (sessionId: string) => {
await backendApi.cancelSession(sessionId);
},
};
}
return {
startTest: async (testName, isRealtime, sender) => {
startSession: async (isRealtime, sender) => {
if (!sender) throw new Error('START_TEST_NO_SENDER');
await jsCoverageRecorder.start(sender);
const sessionId = await backendApi.startTest(testName, isRealtime);
const sessionId = await backendApi.startSession(isRealtime);
return sessionId;
},
stopTest: async (sessionId, testName, sender) => {
addTests: async (sessionId, tests) => {
await backendApi.addTests(sessionId, tests);
},
stopSession: async (sessionId, testName, sender) => {
if (!sender) throw new Error('STOP_TEST_NO_SENDER');
const data = await jsCoverageRecorder.stop(sender);
await backendApi.addSessionData(sessionId, { ...data, testName });
await backendApi.stopTest(sessionId);
await backendApi.addSessionData(sessionId, { ...data, testId: getHash(testName) });
await backendApi.stopSession(sessionId);
},
cancelTest: async (sessionId, sender) => {
cancelSession: async (sessionId, sender) => {
if (!sender) throw new Error('CANCEL_TEST_NO_SENDER');
// TODO that could mask other errors
// quick hack to handle situations when debugger was already disconnected
Expand All @@ -489,7 +513,7 @@ function createAdapter(adapterInfo: AdapterInfo, backend: BackendCreator): Agent
} catch (e) {
console.log('WARNING', 'failed to disconnect debugger:', e);
}
await backendApi.cancelTest(sessionId);
await backendApi.cancelSession(sessionId);
},
};
}
2 changes: 1 addition & 1 deletion src/background/request-interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function setupRequestInterceptor(sessionsStorage: Record<string, SessionD
const session = sessionsStorage[host];
if (session && session.status === SessionStatus.ACTIVE) {
requestHeaders.push({ name: 'drill-session-id', value: session.sessionId });
requestHeaders.push({ name: 'drill-test-name', value: session.testName });
requestHeaders.push({ name: 'drill-test-id', value: session.testId });
}
return { requestHeaders };
};
Expand Down
Loading

0 comments on commit 8e36b3a

Please sign in to comment.