Commit a2dd3862 authored by Paul Slaughter's avatar Paul Slaughter

Setup mock server

Also sets up helpers needed for mock server
parent c0024233
......@@ -8,32 +8,12 @@
*
* See https://gitlab.com/gitlab-org/gitlab/-/issues/208800 for more information.
*/
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { initIde } from '~/ide';
import extendStore from '~/ide/stores/extend';
import { TEST_HOST } from 'helpers/test_constants';
import { useOverclockTimers } from 'test_helpers/utils/overclock_timers';
jest.mock('~/api', () => {
return {
project: jest.fn().mockImplementation(() => new Promise(() => {})),
};
});
jest.mock('~/ide/services/gql', () => {
return {
query: jest.fn().mockImplementation(() => new Promise(() => {})),
};
});
describe('WebIDE', () => {
let vm;
let root;
let mock;
let initData;
let location;
beforeEach(() => {
root = document.createElement('div');
initData = {
const TEST_DATASET = {
emptyStateSvgPath: '/test/empty_state.svg',
noChangesStateSvgPath: '/test/no_changes_state.svg',
committedStateSvgPath: '/test/committed_state.svg',
......@@ -44,57 +24,39 @@ describe('WebIDE', () => {
clientsidePreviewEnabled: 'true',
renderWhitespaceInCode: 'false',
codesandboxBundlerUrl: 'test/codesandbox_bundler',
};
};
mock = new MockAdapter(axios);
mock.onAny('*').reply(() => new Promise(() => {}));
describe('WebIDE', () => {
useOverclockTimers();
let vm;
let root;
beforeEach(() => {
root = document.createElement('div');
document.body.appendChild(root);
location = { pathname: '/-/ide/project/gitlab-test/test', search: '', hash: '' };
Object.defineProperty(window, 'location', {
get() {
return location;
},
global.jsdom.reconfigure({
url: `${TEST_HOST}/-/ide/project/gitlab-test/lorem-ipsum`,
});
});
afterEach(() => {
vm.$destroy();
vm = null;
mock.restore();
root.remove();
});
const createComponent = () => {
const el = document.createElement('div');
Object.assign(el.dataset, initData);
Object.assign(el.dataset, TEST_DATASET);
root.appendChild(el);
vm = initIde(el);
vm = initIde(el, { extendStore });
};
expect.addSnapshotSerializer({
test(value) {
return value instanceof HTMLElement && !value.$_hit;
},
print(element, serialize) {
element.$_hit = true;
element.querySelectorAll('[style]').forEach(el => {
el.$_hit = true;
if (el.style.display === 'none') {
el.textContent = '(jest: contents hidden)';
}
});
return serialize(element)
.replace(/^\s*<!---->$/gm, '')
.replace(/\n\s*\n/gm, '\n');
},
});
it('runs', () => {
createComponent();
return vm.$nextTick().then(() => {
expect(root).toMatchSnapshot();
});
});
});
import { withValues } from '../utils/obj';
import { getCommit } from '../fixtures';
import { createCommitId } from './commit_id';
// eslint-disable-next-line import/prefer-default-export
export const createNewCommit = ({ id = createCommitId(), message }, orig = getCommit()) => {
return withValues(orig, {
id,
short_id: id.substr(0, 8),
message,
title: message,
web_url: orig.web_url.replace(orig.id, id),
parent_ids: [orig.id],
});
};
const COMMIT_ID_LENGTH = 40;
const DEFAULT_COMMIT_ID = Array(COMMIT_ID_LENGTH)
.fill('0')
.join('');
export const createCommitId = (index = 0) =>
`${index}${DEFAULT_COMMIT_ID}`.substr(0, COMMIT_ID_LENGTH);
export const createCommitIdGenerator = () => {
let prevCommitId = 0;
const next = () => {
prevCommitId += 1;
return createCommitId(prevCommitId);
};
return {
next,
};
};
export * from './commit';
export * from './commit_id';
/* eslint-disable global-require */
import { memoize } from 'lodash';
export const getProject = () => require('test_fixtures/api/projects/get.json');
export const getBranch = () => require('test_fixtures/api/projects/branches/get.json');
export const getMergeRequests = () => require('test_fixtures/api/merge_requests/get.json');
export const getRepositoryFiles = () => require('test_fixtures/projects_json/files.json');
export const getPipelinesEmptyResponse = () =>
require('test_fixtures/projects_json/pipelines_empty.json');
export const getCommit = memoize(() => getBranch().commit);
import { buildSchema, graphql } from 'graphql';
import gitlabSchemaStr from '../../../../doc/api/graphql/reference/gitlab_schema.graphql';
const graphqlSchema = buildSchema(gitlabSchemaStr.loc.source.body);
const graphqlResolvers = {
project({ fullPath }, schema) {
const result = schema.projects.findBy({ path_with_namespace: fullPath });
const userPermission = schema.db.userPermissions[0];
return {
...result.attrs,
userPermissions: {
...userPermission,
},
};
},
};
// eslint-disable-next-line import/prefer-default-export
export const graphqlQuery = (query, variables, schema) =>
graphql(graphqlSchema, query, graphqlResolvers, schema, variables);
import { Server, Model, RestSerializer } from 'miragejs';
import { getProject, getBranch, getMergeRequests, getRepositoryFiles } from 'test_helpers/fixtures';
import setupRoutes from './routes';
export const createMockServerOptions = () => ({
models: {
project: Model,
branch: Model,
mergeRequest: Model,
file: Model,
userPermission: Model,
},
serializers: {
application: RestSerializer.extend({
root: false,
}),
},
seeds(schema) {
schema.db.loadData({
files: getRepositoryFiles().map(path => ({ path })),
projects: [getProject()],
branches: [getBranch()],
mergeRequests: getMergeRequests(),
userPermissions: [
{
createMergeRequestIn: true,
readMergeRequest: true,
pushCode: true,
},
],
});
},
routes() {
this.namespace = '';
this.urlPrefix = '/';
setupRoutes(this);
},
});
export const createMockServer = () => {
const server = new Server(createMockServerOptions());
return server;
};
export default server => {
['get', 'post', 'put', 'delete', 'patch'].forEach(method => {
server[method]('*', () => {
return new Response(404);
});
});
};
import { getPipelinesEmptyResponse } from 'test_helpers/fixtures';
export default server => {
server.get('*/commit/:id/pipelines', () => {
return getPipelinesEmptyResponse();
});
server.get('/api/v4/projects/:id/runners', () => {
return [];
});
};
import { graphqlQuery } from '../graphql';
export default server => {
server.post('/api/graphql', (schema, request) => {
const batches = JSON.parse(request.requestBody);
return Promise.all(
batches.map(({ query, variables }) => graphqlQuery(query, variables, schema)),
);
});
};
/* eslint-disable global-require */
export default server => {
[
require('./graphql'),
require('./projects'),
require('./repository'),
require('./ci'),
require('./404'),
].forEach(({ default: setup }) => {
setup(server);
});
};
import { withKeys } from 'test_helpers/utils/obj';
export default server => {
server.get('/api/v4/projects/:id', (schema, request) => {
const { id } = request.params;
const proj =
schema.projects.findBy({ id }) ?? schema.projects.findBy({ path_with_namespace: id });
return proj.attrs;
});
server.get('/api/v4/projects/:id/merge_requests', (schema, request) => {
const result = schema.mergeRequests.where(
withKeys(request.queryParams, {
source_project_id: 'project_id',
source_branch: 'source_branch',
}),
);
return result.models;
});
};
import { createNewCommit, createCommitIdGenerator } from 'test_helpers/factories';
export default server => {
const commitIdGenerator = createCommitIdGenerator();
server.get('/api/v4/projects/:id/repository/branches', schema => {
return schema.db.branches;
});
server.get('/api/v4/projects/:id/repository/branches/:name', (schema, request) => {
const { name } = request.params;
const branch = schema.branches.findBy({ name });
return branch.attrs;
});
server.get('*/-/files/:id', schema => {
return schema.db.files.map(({ path }) => path);
});
server.post('/api/v4/projects/:id/repository/commits', (schema, request) => {
const { branch: branchName, commit_message: message, actions } = JSON.parse(
request.requestBody,
);
const branch = schema.branches.findBy({ name: branchName });
const commit = {
...createNewCommit({ id: commitIdGenerator.next(), message }, branch.attrs.commit),
__actions: actions,
};
branch.update({ commit });
return commit;
});
};
import { createMockServer } from './index';
if (process.env.NODE_ENV === 'development') {
window.mockServer = createMockServer();
}
......@@ -2,3 +2,4 @@ import '../../../frontend/test_setup';
import './setup_globals';
import './setup_axios';
import './setup_serializers';
import './setup_mock_server';
import { createMockServer } from '../mock_server';
beforeEach(() => {
const server = createMockServer();
server.logging = false;
global.mockServer = server;
});
afterEach(() => {
global.mockServer.shutdown();
global.mockServer = null;
});
import { has, mapKeys, pick } from 'lodash';
/**
* This method is used to type-safely set values on the given object
*
* @template T
* @returns {T} A shallow copy of `obj`, with the values from `values`
* @throws {Error} If `values` contains a key that isn't already on `obj`
* @param {T} source
* @param {Object} values
*/
export const withValues = (source, values) =>
Object.entries(values).reduce(
(acc, [key, value]) => {
if (!has(acc, key)) {
throw new Error(
`[mock_server] Cannot write property that does not exist on object '${key}'`,
);
}
return {
...acc,
[key]: value,
};
},
{ ...source },
);
/**
* This method returns a subset of the given object and maps the key names based on the
* given `keys`.
*
* @param {Object} obj The source object.
* @param {Object} map The object which contains the keys to use and mapped key names.
*/
export const withKeys = (obj, map) => mapKeys(pick(obj, Object.keys(map)), (val, key) => map[key]);
import { withKeys, withValues } from './obj';
describe('frontend_integration/test_helpers/utils/obj', () => {
describe('withKeys', () => {
it('picks and maps keys', () => {
expect(withKeys({ a: '123', b: 456, c: 'd' }, { b: 'lorem', c: 'ipsum', z: 'zed ' })).toEqual(
{ lorem: 456, ipsum: 'd' },
);
});
});
describe('withValues', () => {
it('sets values', () => {
expect(withValues({ a: '123', b: 456 }, { b: 789 })).toEqual({ a: '123', b: 789 });
});
it('throws if values has non-existent key', () => {
expect(() => withValues({ a: '123', b: 456 }, { b: 789, bogus: 'throws' })).toThrow(
`[mock_server] Cannot write property that does not exist on object 'bogus'`,
);
});
});
});
// eslint-disable-next-line import/prefer-default-export
export const useOverclockTimers = (boost = 50) => {
if (boost <= 0) {
throw new Error(`[overclock_timers] boost (${boost}) cannot be <= 0`);
}
let origSetTimeout;
let origSetInterval;
const newSetTimeout = (fn, msParam = 0) => {
const ms = msParam > 0 ? Math.floor(msParam / boost) : msParam;
return origSetTimeout(fn, ms);
};
const newSetInterval = (fn, msParam = 0) => {
const ms = msParam > 0 ? Math.floor(msParam / boost) : msParam;
return origSetInterval(fn, ms);
};
beforeEach(() => {
origSetTimeout = global.setTimeout;
origSetInterval = global.setInterval;
global.setTimeout = newSetTimeout;
global.setInterval = newSetInterval;
});
afterEach(() => {
global.setTimeout = origSetTimeout;
global.setInterval = origSetInterval;
});
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment