Skip to content

Commit

Permalink
refactor(plugin): extract loader and exporter to separate classes
Browse files Browse the repository at this point in the history
closes #163
  • Loading branch information
derevnjuk committed Jan 23, 2023
1 parent 50b6af1 commit 278698a
Show file tree
Hide file tree
Showing 12 changed files with 468 additions and 161 deletions.
135 changes: 63 additions & 72 deletions src/Plugin.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import type { RecordOptions, SaveOptions } from './Plugin';
import { Plugin } from './Plugin';
import { Logger } from './utils/Logger';
import { FileManager } from './utils/FileManager';
import type {
Observer,
ObserverFactory,
HarExporter,
HarExporterFactory
} from './network';
import { NetworkRequest } from './network';
import type { RecordOptions, SaveOptions } from './Plugin';
import type { Connection, ConnectionFactory } from './cdp';
import type { Observer, ObserverFactory } from './network';
import {
anyFunction,
anyString,
Expand All @@ -20,8 +25,7 @@ import {
} from 'ts-mockito';
import { beforeEach, describe, expect, it, jest } from '@jest/globals';
import type { Entry } from 'har-format';
import { WriteStream } from 'fs';
import { EOL, tmpdir } from 'os';
import { tmpdir } from 'os';

const resolvableInstance = <T extends object>(m: T): T =>
new Proxy<T>(instance(m), {
Expand Down Expand Up @@ -110,8 +114,9 @@ describe('Plugin', () => {
const observerFactoryMock = mock<ObserverFactory>();
const networkObserverMock = mock<Observer<NetworkRequest>>();
const connectionFactoryMock = mock<ConnectionFactory>();
const harExporterFactoryMock = mock<HarExporterFactory>();
const harExporterMock = mock<HarExporter>();
const connectionMock = mock<Connection>();
const writableStreamMock = mock<WriteStream>();
const processEnv = process.env;

let plugin!: Plugin;
Expand All @@ -131,7 +136,8 @@ describe('Plugin', () => {
instance(loggerMock),
instance(fileManagerMock),
instance(connectionFactoryMock),
instance(observerFactoryMock)
instance(observerFactoryMock),
instance(harExporterFactoryMock)
);
});

Expand All @@ -142,10 +148,11 @@ describe('Plugin', () => {
| Logger
| FileManager
| Connection
| WriteStream
| ConnectionFactory
| ObserverFactory
| Observer<NetworkRequest>
| HarExporter
| HarExporterFactory
>(
processSpy,
loggerMock,
Expand All @@ -154,7 +161,8 @@ describe('Plugin', () => {
connectionFactoryMock,
observerFactoryMock,
networkObserverMock,
writableStreamMock
harExporterFactoryMock,
harExporterMock
);
});

Expand Down Expand Up @@ -227,13 +235,6 @@ describe('Plugin', () => {
outDir: tmpdir()
} as SaveOptions;

it(`should return undefined to satisfy Cypress's contract when connection is not established yet`, async () => {
// act
const result = await plugin.saveHar(options);
// assert
expect(result).toBeUndefined();
});

it('should log an error message when the connection is corrupted', async () => {
// act
await plugin.saveHar(options);
Expand Down Expand Up @@ -271,12 +272,10 @@ describe('Plugin', () => {
observerFactoryMock.createNetworkObserver(anything(), anything())
).thenReturn(instance(networkObserverMock));
when(networkObserverMock.empty).thenReturn(false, false, true);
when(fileManagerMock.createTmpWriteStream()).thenResolve(
resolvableInstance(writableStreamMock)
when(harExporterFactoryMock.create(anything())).thenResolve(
resolvableInstance(harExporterMock)
);
// @ts-expect-error type mismatch
when(writableStreamMock.closed).thenReturn(true);
when(writableStreamMock.path).thenReturn('temp-file.txt');
when(harExporterMock.path).thenReturn('temp-file.txt');
plugin.ensureBrowserFlags(chrome, []);
await plugin.recordHar({
rootDir: '/'
Expand All @@ -302,12 +301,10 @@ describe('Plugin', () => {
when(
observerFactoryMock.createNetworkObserver(anything(), anything())
).thenReturn(instance(networkObserverMock));
when(fileManagerMock.createTmpWriteStream()).thenResolve(
resolvableInstance(writableStreamMock)
when(harExporterFactoryMock.create(anything())).thenResolve(
resolvableInstance(harExporterMock)
);
// @ts-expect-error type mismatch
when(writableStreamMock.closed).thenReturn(true);
when(writableStreamMock.path).thenReturn('temp-file.txt');
when(harExporterMock.path).thenReturn('temp-file.txt');
plugin.ensureBrowserFlags(chrome, []);
await plugin.recordHar({
rootDir: '/'
Expand Down Expand Up @@ -335,12 +332,10 @@ describe('Plugin', () => {
when(
observerFactoryMock.createNetworkObserver(anything(), anything())
).thenReturn(instance(networkObserverMock));
when(fileManagerMock.createTmpWriteStream()).thenResolve(
resolvableInstance(writableStreamMock)
when(harExporterFactoryMock.create(anything())).thenResolve(
resolvableInstance(harExporterMock)
);
// @ts-expect-error type mismatch
when(writableStreamMock.closed).thenReturn(true);
when(writableStreamMock.path).thenReturn('temp-file.txt');
when(harExporterMock.path).thenReturn('temp-file.txt');
plugin.ensureBrowserFlags(chrome, []);
await plugin.recordHar({
rootDir: '/'
Expand Down Expand Up @@ -378,21 +373,19 @@ describe('Plugin', () => {
verify(networkObserverMock.unsubscribe()).once();
});

it('should dispose a stream', async () => {
it('should dispose a exporter', async () => {
// arrange
when(connectionFactoryMock.create(anything())).thenReturn(
instance(connectionMock)
);
when(
observerFactoryMock.createNetworkObserver(anything(), anything())
).thenReturn(instance(networkObserverMock));
when(fileManagerMock.createTmpWriteStream()).thenResolve(
resolvableInstance(writableStreamMock)
when(harExporterFactoryMock.create(anything())).thenResolve(
resolvableInstance(harExporterMock)
);
const tempFilePath = 'temp-file.txt';
// @ts-expect-error type mismatch
when(writableStreamMock.closed).thenReturn(true);
when(writableStreamMock.path).thenReturn(tempFilePath);
when(harExporterMock.path).thenReturn(tempFilePath);
plugin.ensureBrowserFlags(chrome, []);
await plugin.recordHar({
rootDir: '/'
Expand All @@ -404,7 +397,7 @@ describe('Plugin', () => {
// act
await plugin.saveHar(options);
// assert
verify(writableStreamMock.end()).once();
verify(harExporterMock.end()).once();
verify(fileManagerMock.removeFile(tempFilePath)).once();
});
});
Expand Down Expand Up @@ -442,6 +435,15 @@ describe('Plugin', () => {
verify(networkObserverMock.subscribe(anyFunction())).once();
});

it('should throw an error when the addr is not defined', async () => {
// act
const act = () => plugin.recordHar(options);
// assert
await expect(act).rejects.toThrow(
"Please call the 'ensureBrowserFlags' before attempting to start the recording."
);
});

it('should close connection when it is already opened', async () => {
// arrange
plugin.ensureBrowserFlags(chrome, []);
Expand All @@ -453,52 +455,43 @@ describe('Plugin', () => {
verify(connectionMock.open()).twice();
});

it('should write an entry to a stream', async () => {
it('should pass an entry to a exporter', async () => {
// arrange
when(fileManagerMock.createTmpWriteStream()).thenResolve(
resolvableInstance(writableStreamMock)
const request = new NetworkRequest(
'1',
'https://example.com',
'https://example.com',
'1'
);
when(harExporterFactoryMock.create(anything())).thenResolve(
resolvableInstance(harExporterMock)
);
// @ts-expect-error type mismatch
when(writableStreamMock.closed).thenReturn(false);
when(networkObserverMock.subscribe(anyFunction())).thenCall(callback =>
callback(
new NetworkRequest(
'1',
'https://example.com',
'https://example.com',
'1'
)
)
callback(request)
);
plugin.ensureBrowserFlags(chrome, []);
// act
await plugin.recordHar(options);
// assert
verify(writableStreamMock.write(match(`${EOL}`))).once();
verify(harExporterMock.write(request)).once();
});

it('should do nothing when a stream is closed', async () => {
it('should do nothing when a exporter is not defined', async () => {
// arrange
when(fileManagerMock.createTmpWriteStream()).thenResolve(
resolvableInstance(writableStreamMock)
const request = new NetworkRequest(
'1',
'https://example.com',
'https://example.com',
'1'
);
// @ts-expect-error type mismatch
when(writableStreamMock.closed).thenReturn(true);
when(networkObserverMock.subscribe(anyFunction())).thenCall(callback =>
callback(
new NetworkRequest(
'1',
'https://example.com',
'https://example.com',
'1'
)
)
callback(request)
);
plugin.ensureBrowserFlags(chrome, []);
// act
await plugin.recordHar(options);
// assert
verify(writableStreamMock.write(match(`${EOL}`))).never();
verify(harExporterMock.write(request)).never();
});
});

Expand All @@ -510,15 +503,13 @@ describe('Plugin', () => {
when(
observerFactoryMock.createNetworkObserver(anything(), anything())
).thenReturn(instance(networkObserverMock));
when(fileManagerMock.createTmpWriteStream()).thenResolve(
resolvableInstance(writableStreamMock)
when(harExporterFactoryMock.create(anything())).thenResolve(
resolvableInstance(harExporterMock)
);
// @ts-expect-error type mismatch
when(writableStreamMock.closed).thenReturn(true);
when(writableStreamMock.path).thenReturn('temp-file.txt');
when(harExporterMock.path).thenReturn('temp-file.txt');
});

it('should dispose of a stream', async () => {
it('should dispose of a exporter', async () => {
// arrange
plugin.ensureBrowserFlags(chrome, []);
await plugin.recordHar({
Expand All @@ -527,7 +518,7 @@ describe('Plugin', () => {
// act
await plugin.disposeOfHar();
// assert
verify(writableStreamMock.end()).once();
verify(harExporterMock.end()).once();
});

it('should unsubscribe from the network events', async () => {
Expand Down
Loading

0 comments on commit 278698a

Please sign in to comment.