Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: adds setup for mock-service-worker #127

Merged
merged 5 commits into from
Dec 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
"@types/dom-helpers": "^3.4.1",
"@types/express": "^4.17.2",
"@types/html-webpack-plugin": "^2.28.0",
"@types/jest": "^25.2.1",
"@types/jest": "^26.0.0",
"@types/js-yaml": "^3.10.1",
"@types/linkify-it": "^2.1.0",
"@types/lodash": "^4.14.68",
Expand Down Expand Up @@ -138,7 +138,7 @@
"axios": "^0.18.1",
"axios-mock-adapter": "^1.16.0",
"babel-core": "^7.0.0-0",
"babel-jest": "^25.5.0",
"babel-jest": "^26.0.0",
"babel-loader": "^8.0.0-beta.2",
"camelcase-keys": "^6.1.1",
"classnames": "^2.2.6",
Expand All @@ -160,13 +160,14 @@
"husky": "^4.2.5",
"identity-obj-proxy": "^3.0.0",
"intersection-observer": "^0.7.0",
"jest": "^25.5.0",
"jest": "^26.0.0",
"linkify-it": "^2.2.0",
"lint-staged": "^7.0.4",
"lossless-json": "^1.0.3",
"memoize-one": "^5.0.0",
"moment": "^2.18.1",
"moment-timezone": "^0.5.28",
"msw": "^0.24.1",
"object-hash": "^1.3.1",
"prettier": "1.19.1",
"protobufjs": "~6.8.0",
Expand All @@ -189,15 +190,15 @@
"snakecase-keys": "^3.1.0",
"source-map-loader": "^0.2.1",
"storybook-react-router": "^1.0.5",
"ts-jest": "^25.4.0",
"ts-jest": "^26.3.0",
"ts-loader": "^6.2.1",
"ts-node": "^8.0.2",
"tslint": "^5.20.1",
"tslint-config-airbnb": "^5.11.2",
"tslint-config-prettier": "^1.18.0",
"tslint-plugin-prettier": "^2.0.1",
"tslint-react": "^4.1.0",
"typescript": "^3.8.3",
"typescript": "^4.0.0",
"uglifyjs-webpack-plugin": "^1.2.5",
"url-search-params": "^0.10.0",
"use-react-router": "^1.0.7",
Expand Down
11 changes: 1 addition & 10 deletions src/common/env.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
import { Env } from 'config';

/** Safely check for server environment */
export function isServer() {
try {
return __isServer;
} catch (e) {
return false;
}
}

/** equivalent to process.env in server and client */
// tslint:disable-next-line:no-any
export const env: Env = (isServer() ? process.env : window.env) as any;
export const env: Env = Object.assign({}, process.env, window.env);
4 changes: 3 additions & 1 deletion src/common/test/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import {
timestampToDate
} from '../utils';

jest.mock('common/env');
jest.mock('common/env', () => ({
env: jest.requireActual('common/env').env
}));

import { Protobuf } from 'flyteidl';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import * as React from 'react';

import { SectionHeader } from 'components/common';
import { useCommonStyles } from 'components/common/styles';
import { NodeDetailsProps } from 'components/WorkflowGraph';
import { useStyles as useBaseStyles } from 'components/WorkflowGraph/NodeDetails/styles';

import { LiteralMapViewer } from 'components/Literals';
import * as React from 'react';
import { ExecutionContext } from '../../contexts';

/** Details panel renderer for the start/input node in a graph. Displays the
Expand All @@ -23,15 +20,7 @@ export const InputNodeDetails: React.FC<NodeDetailsProps> = () => {
<SectionHeader title="Execution Inputs" />
</div>
</header>
<div className={baseStyles.content}>
<div className={commonStyles.detailsPanelCard}>
<div className={commonStyles.detailsPanelCardContent}>
<LiteralMapViewer
map={execution.closure.computedInputs}
/>
</div>
</div>
</div>
<div className={baseStyles.content} />
</section>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ describe.skip('ExecutionNodeViews', () => {
props = { execution: workflowExecution };
});

it('is disabled', () => {});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these intentionally kept in?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. This keeps the test runner from throwing errors. I will remove them in the next PR when I fix and re-enable the commented-out tests.


const renderViews = () =>
render(
<QueryClientProvider client={queryClient}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ describe.skip('NodeExecutionsTable', () => {
return render(<Table {...await getProps()} />);
};

it('is disabled', () => {});

// it('renders task name for task nodes', async () => {
// const { queryAllByText, getAllByRole } = await renderTable();
// await waitFor(() => getAllByRole('listitem').length > 0);
Expand Down
15 changes: 0 additions & 15 deletions src/components/Executions/test/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,6 @@ describe('getWorkflowExecutionTimingMS', () => {
expect(getWorkflowExecutionTimingMS(execution)).toBeNull();
});

it('should return null when no createdAt field is present', () => {
delete execution.closure.createdAt;
expect(getWorkflowExecutionTimingMS(execution)).toBeNull();
});

it('should return null when no startedAt field is present', () => {
delete execution.closure.startedAt;
expect(getWorkflowExecutionTimingMS(execution)).toBeNull();
Expand Down Expand Up @@ -103,11 +98,6 @@ describe('getNodeExecutionTimingMS', () => {
expect(getNodeExecutionTimingMS(execution)).toBeNull();
});

it('should return null when no createdAt field is present', () => {
delete execution.closure.createdAt;
expect(getNodeExecutionTimingMS(execution)).toBeNull();
});

it('should return null when no startedAt field is present', () => {
delete execution.closure.startedAt;
expect(getNodeExecutionTimingMS(execution)).toBeNull();
Expand Down Expand Up @@ -151,11 +141,6 @@ describe('getTaskExecutionTimingMS', () => {
expect(getTaskExecutionTimingMS(execution)).toBeNull();
});

it('should return null when no createdAt field is present', () => {
delete execution.closure.createdAt;
expect(getTaskExecutionTimingMS(execution)).toBeNull();
});

it('should return null when no startedAt field is present', () => {
delete execution.closure.startedAt;
expect(getTaskExecutionTimingMS(execution)).toBeNull();
Expand Down
6 changes: 6 additions & 0 deletions src/mocks/data/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Arbitrary start date used as a basis for generating timestamps
// to keep the mocked data consistent across runs
export const mockStartDate = new Date('2020-11-15T02:32:19.610Z');

// Workflow Execution duration in milliseconds
export const defaultWorkflowExecutionDuration = 1000 * 60 * 60 * 1.251;
15 changes: 15 additions & 0 deletions src/mocks/data/launchPlans.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Core } from 'flyteidl';
import { LaunchPlan } from 'models';

// TODO:
const basic: LaunchPlan = {
id: {
resourceType: Core.ResourceType.LAUNCH_PLAN,
project: 'flytetest',
domain: 'development',
name: 'Basic',
version: 'abc123'
}
} as LaunchPlan;

export const launchPlans = { basic };
Empty file.
8 changes: 8 additions & 0 deletions src/mocks/data/projects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export function emptyProject(id: string, name?: string) {
return {
id,
name: name ?? id,
domains: [],
description: ''
};
}
48 changes: 48 additions & 0 deletions src/mocks/data/workflowExecutions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { dateToTimestamp, millisecondsToDuration } from 'common/utils';
import { Admin } from 'flyteidl';
import { LiteralMap } from 'models/Common/types';
import { WorkflowExecutionPhase } from 'models/Execution/enums';
import { Execution, ExecutionMetadata } from 'models/Execution/types';
import { defaultWorkflowExecutionDuration, mockStartDate } from './constants';
import { launchPlans } from './launchPlans';
import { workflows } from './workflows';

export function defaultWorkflowExecutionMetadata(): ExecutionMetadata {
return {
mode: Admin.ExecutionMetadata.ExecutionMode.MANUAL,
principal: 'sdk',
nesting: 0
};
}

export function emptyLiteralMap(): LiteralMap {
return { literals: {} };
}

const basic: Execution = {
id: {
project: 'flytetest',
domain: 'development',
name: 'abc123'
},
spec: {
launchPlan: { ...launchPlans.basic.id },
inputs: emptyLiteralMap(),
metadata: defaultWorkflowExecutionMetadata(),
notifications: {
notifications: []
}
},
closure: {
computedInputs: emptyLiteralMap(),
createdAt: dateToTimestamp(mockStartDate),
duration: millisecondsToDuration(defaultWorkflowExecutionDuration),
phase: WorkflowExecutionPhase.SUCCEEDED,
startedAt: dateToTimestamp(mockStartDate),
workflowId: { ...workflows.basic.id }
}
};

export const workflowExecutions = {
basic
};
15 changes: 15 additions & 0 deletions src/mocks/data/workflows.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Core } from 'flyteidl';
import { Workflow } from 'models/Workflow/types';

// TODO:
const basic: Workflow = {
id: {
resourceType: Core.ResourceType.WORKFLOW,
project: 'flytetest',
domain: 'development',
name: 'Basic',
version: 'abc123'
}
};

export const workflows = { basic };
10 changes: 10 additions & 0 deletions src/mocks/getDefaultData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { RequestHandlersList } from 'msw/lib/types/setupWorker/glossary';
import { workflowExecutions } from './data/workflowExecutions';
import { workflowExecutionHandler } from './handlers';

export function getDefaultData(): RequestHandlersList {
const workflowExecutionHandlers = Object.values(workflowExecutions).map(
workflowExecutionHandler
);
return [...workflowExecutionHandlers];
}
102 changes: 102 additions & 0 deletions src/mocks/handlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { Admin } from 'flyteidl';
import {
EncodableType,
encodeProtoPayload,
Execution,
NameIdentifierScope,
NodeExecution,
Project,
Workflow
} from 'models';
import {
makeExecutionPath,
makeNodeExecutionListPath,
makeNodeExecutionPath
} from 'models/Execution/utils';
import { makeWorkflowPath } from 'models/Workflow/utils';
import { ResponseResolver, rest } from 'msw';
import { setupServer } from 'msw/lib/types/node';
import { RestContext } from 'msw/lib/types/rest';
import { apiPath } from './utils';

export function adminEntityResponder(
data: any,
encodeType: EncodableType<any>
): ResponseResolver<any, RestContext> {
const buffer = encodeProtoPayload(data, encodeType);
const contentLength = buffer.byteLength.toString();
return (_, res, ctx) =>
res(
ctx.set('Content-Type', 'application/octet-stream'),
ctx.set('Content-Length', contentLength),
ctx.body(buffer)
);
}

export function workflowExecutionHandler(data: Partial<Execution>) {
return rest.get(
apiPath(makeExecutionPath(data.id!)),
adminEntityResponder(data, Admin.Execution)
);
}

export function workflowHandler(data: Partial<Workflow>) {
return rest.get(
apiPath(makeWorkflowPath(data.id!)),
adminEntityResponder(data, Admin.Workflow)
);
}

export function nodeExecutionHandler(data: Partial<NodeExecution>) {
return rest.get(
apiPath(makeNodeExecutionPath(data.id!)),
adminEntityResponder(data, Admin.NodeExecution)
);
}

// TODO: pagination responder that respects limit/token?
export function nodeExecutionListHandler(
scope: NameIdentifierScope,
data: Partial<NodeExecution>[]
) {
return rest.get(
apiPath(makeNodeExecutionListPath(scope)),
adminEntityResponder(
{
nodeExecutions: data
},
Admin.NodeExecutionList
)
);
}

export function projectListHandler(data: Project[]) {
return rest.get(
apiPath('/projects'),
adminEntityResponder({ projects: data }, Admin.Projects)
);
}

export interface BoundAdminServer {
insertNodeExecution(data: Partial<NodeExecution>): void;
insertNodeExecutionList(
scope: NameIdentifierScope,
data: Partial<NodeExecution>[]
): void;
insertProjects(data: Project[]): void;
insertWorkflow(data: Partial<Workflow>): void;
insertWorkflowExecution(data: Partial<Execution>): void;
}

export function bindHandlers({
use
}: ReturnType<typeof setupServer>): BoundAdminServer {
return {
insertNodeExecution: data => use(nodeExecutionHandler(data)),
insertNodeExecutionList: (scope, data) =>
use(nodeExecutionListHandler(scope, data)),
insertProjects: data => use(projectListHandler(data)),
insertWorkflow: data => use(workflowHandler(data)),
insertWorkflowExecution: data => use(workflowExecutionHandler(data))
};
}
7 changes: 7 additions & 0 deletions src/mocks/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { setupServer } from 'msw/node';
import { getDefaultData } from './getDefaultData';
import { bindHandlers } from './handlers';

const server = setupServer(...getDefaultData());
const handlers = bindHandlers(server);
export const mockServer = { ...server, ...handlers };
5 changes: 5 additions & 0 deletions src/mocks/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { apiPrefix } from 'models/AdminEntity/constants';

export function apiPath(path: string) {
return `${apiPrefix}${path}`;
}
2 changes: 2 additions & 0 deletions src/models/AdminEntity/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { RequestConfig } from './types';

export const apiPrefix = '/api/v1';

export const limits = {
DEFAULT: 25,
/** The admin API requires a limit value for all list endpoints, but does not
Expand Down
Loading