提交 0ac3edb7 编写于 作者: T Tomas Vik

Merge branch '220-add-tests-to-gitlab-service' into 'main'

test(git_service): remove circular dependency and add tests

See merge request gitlab-org/gitlab-vscode-extension!121
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
"--disable-extensions", "--disable-extensions",
"--extensionDevelopmentPath=${workspaceRoot}", "--extensionDevelopmentPath=${workspaceRoot}",
"--extensionTestsPath=${workspaceRoot}/out/test/integration/", "--extensionTestsPath=${workspaceRoot}/out/test/integration/",
"<paste the last line of `npm run create-test-workspace` output here>" "<run `npm run create-test-workspace` to generate a test folder>"
], ],
"preLaunchTask": "${defaultBuildTask}", "preLaunchTask": "${defaultBuildTask}",
"stopOnEntry": false "stopOnEntry": false
......
...@@ -43,7 +43,7 @@ We use [`msw`](https://mswjs.io/docs/) to intercept any requests and return prep ...@@ -43,7 +43,7 @@ We use [`msw`](https://mswjs.io/docs/) to intercept any requests and return prep
### Debugging integration tests ### Debugging integration tests
For debugging of the integration tests, we first need to create a test workspace (the `npm run test-integration` task doesn't need this step because it does it automatically). We can do that by running ```npm run create-test-workspace``` script. Then we copy the output to `.vscode/launch.json` instead of the placeholder in the "Integration Tests" launch configuration arguments. For debugging of the integration tests, we first need to create a test workspace (the `npm run test-integration` task doesn't need this step because it does it automatically). We can do that by running ```npm run create-test-workspace``` script. This script pastes the reference to generated workspace in `.vscode/launch.json`.
Then we can debug the by running the "Integration Tests" [Launch configuration]. Then we can debug the by running the "Integration Tests" [Launch configuration].
......
...@@ -1377,6 +1377,18 @@ ...@@ -1377,6 +1377,18 @@
"@babel/types": "^7.3.0" "@babel/types": "^7.3.0"
} }
}, },
"@types/bluebird": {
"version": "3.5.32",
"resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.32.tgz",
"integrity": "sha512-dIOxFfI0C+jz89g6lQ+TqhGgPQ0MxSnh/E4xuC0blhFtyW269+mPG5QeLgbdwst/LvdP8o1y0o/Gz5EHXLec/g==",
"dev": true
},
"@types/caseless": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz",
"integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==",
"dev": true
},
"@types/color-name": { "@types/color-name": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
...@@ -1475,6 +1487,56 @@ ...@@ -1475,6 +1487,56 @@
"integrity": "sha512-boy4xPNEtiw6N3abRhBi/e7hNvy3Tt8E9ZRAQrwAGzoCGZS/1wjo9KY7JHhnfnEsG5wSjDbymCozUM9a3ea7OQ==", "integrity": "sha512-boy4xPNEtiw6N3abRhBi/e7hNvy3Tt8E9ZRAQrwAGzoCGZS/1wjo9KY7JHhnfnEsG5wSjDbymCozUM9a3ea7OQ==",
"dev": true "dev": true
}, },
"@types/request": {
"version": "2.48.5",
"resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.5.tgz",
"integrity": "sha512-/LO7xRVnL3DxJ1WkPGDQrp4VTV1reX9RkC85mJ+Qzykj2Bdw+mG15aAfDahc76HtknjzE16SX/Yddn6MxVbmGQ==",
"dev": true,
"requires": {
"@types/caseless": "*",
"@types/node": "*",
"@types/tough-cookie": "*",
"form-data": "^2.5.0"
},
"dependencies": {
"form-data": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz",
"integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==",
"dev": true,
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.6",
"mime-types": "^2.1.12"
}
}
}
},
"@types/request-promise": {
"version": "4.1.46",
"resolved": "https://registry.npmjs.org/@types/request-promise/-/request-promise-4.1.46.tgz",
"integrity": "sha512-3Thpj2Va5m0ji3spaCk8YKrjkZyZc6RqUVOphA0n/Xet66AW/AiOAs5vfXhQIL5NmkaO7Jnun7Nl9NEjJ2zBaw==",
"dev": true,
"requires": {
"@types/bluebird": "*",
"@types/request": "*"
}
},
"@types/sinon": {
"version": "9.0.4",
"resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.4.tgz",
"integrity": "sha512-sJmb32asJZY6Z2u09bl0G2wglSxDlROlAejCjsnor+LzBMz17gu8IU7vKC/vWDnv9zEq2wqADHVXFjf4eE8Gdw==",
"dev": true,
"requires": {
"@types/sinonjs__fake-timers": "*"
}
},
"@types/sinonjs__fake-timers": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz",
"integrity": "sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA==",
"dev": true
},
"@types/stack-utils": { "@types/stack-utils": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
...@@ -1490,6 +1552,12 @@ ...@@ -1490,6 +1552,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"@types/tough-cookie": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.0.tgz",
"integrity": "sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==",
"dev": true
},
"@types/vscode": { "@types/vscode": {
"version": "1.46.0", "version": "1.46.0",
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.46.0.tgz", "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.46.0.tgz",
......
...@@ -232,7 +232,7 @@ ...@@ -232,7 +232,7 @@
"minItems": 1, "minItems": 1,
"items": { "items": {
"type": "object", "type": "object",
"title": "Custom GitLab Quety", "title": "Custom GitLab Query",
"required": [ "required": [
"name" "name"
], ],
...@@ -347,22 +347,22 @@ ...@@ -347,22 +347,22 @@
"createdAfter": { "createdAfter": {
"type": "string", "type": "string",
"format": "date", "format": "date",
"description": "Return GitLab items created after the given date. It is not applicable for vulnerabilities" "description": "Return GitLab items created after the given date. ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z. It is not applicable for vulnerabilities"
}, },
"createdBefore": { "createdBefore": {
"type": "string", "type": "string",
"format": "date", "format": "date",
"description": "Return GitLab items created before the given date. It is not applicable for vulnerabilities" "description": "Return GitLab items created before the given date. ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z. It is not applicable for vulnerabilities"
}, },
"updatedAfter": { "updatedAfter": {
"type": "string", "type": "string",
"format": "date", "format": "date",
"description": "Return GitLab items updated after the given date. It is not applicable for vulnerabilities" "description": "Return GitLab items updated after the given date. ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z. It is not applicable for vulnerabilities"
}, },
"updatedBefore": { "updatedBefore": {
"type": "string", "type": "string",
"format": "date", "format": "date",
"description": "Return GitLab items updated before the given date. It is not applicable for vulnerabilities" "description": "Return GitLab items updated before the given date. ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z. It is not applicable for vulnerabilities"
}, },
"wip": { "wip": {
"type": "string", "type": "string",
...@@ -522,6 +522,8 @@ ...@@ -522,6 +522,8 @@
"devDependencies": { "devDependencies": {
"@types/jest": "^26.0.14", "@types/jest": "^26.0.14",
"@types/node": "^13.7.0", "@types/node": "^13.7.0",
"@types/request-promise": "^4.1.46",
"@types/sinon": "^9.0.4",
"@types/temp": "^0.8.34", "@types/temp": "^0.8.34",
"@types/vscode": "^1.41.0", "@types/vscode": "^1.41.0",
"@typescript-eslint/eslint-plugin": "^3.7.0", "@typescript-eslint/eslint-plugin": "^3.7.0",
......
#!/usr/bin/env node #!/usr/bin/env node
// This script creates a temporary workspace that can be used for debugging integration tests // This script creates a temporary workspace that can be used for debugging integration tests
const { readFileSync, writeFileSync } = require('fs');
const path = require('path');
const { default: createTmpWorkspace } = require('../out/create_tmp_workspace'); const { default: createTmpWorkspace } = require('../out/create_tmp_workspace');
createTmpWorkspace(false).then(console.log); const PLACEHOLDER = `<run \`npm run create-test-workspace\` to generate a test folder>`;
createTmpWorkspace(false).then(workspaceFolder => {
const launchPath = path.resolve(__dirname, '../.vscode/launch.json');
const tasksContent = readFileSync(launchPath, 'UTF-8');
const tasks = tasksContent.replace(PLACEHOLDER, workspaceFolder);
writeFileSync(launchPath, tasks);
});
const { StatusCodeError } = require('request-promise/errors'); import { StatusCodeError } from 'request-promise/errors';
const { prettyJson, stackToArray } = require('./common'); import { prettyJson, stackToArray, IDetailedError } from './common';
class ApiError extends Error { export class ApiError extends Error implements IDetailedError {
constructor(error, action) { originalError: Error;
super(error);
action: string;
message: string;
constructor(error: Error, action: string) {
super(error.message);
this.action = action; this.action = action;
this.message = `API request failed when trying to ${this.action} because: ${error.message}`; this.message = `API request failed when trying to ${this.action} because: ${error.message}`;
this.originalError = error; this.originalError = error;
} }
get requestDetails() { private get requestDetails() {
if (!(this.originalError instanceof StatusCodeError)) return {}; if (!(this.originalError instanceof StatusCodeError)) return {};
const { method, url } = this.originalError.options; const { method } = this.originalError.options;
// The url parameter exists, but the types are not complete
// eslint-disable-next-line
// @ts-ignore
const { url } = this.originalError.options;
const { response } = this.originalError; const { response } = this.originalError;
return { return {
request: { method, url }, request: { method, url },
...@@ -19,7 +29,7 @@ class ApiError extends Error { ...@@ -19,7 +29,7 @@ class ApiError extends Error {
}; };
} }
get details() { get details(): string {
const { message, stack } = this; const { message, stack } = this;
return prettyJson({ return prettyJson({
message, message,
...@@ -28,5 +38,3 @@ class ApiError extends Error { ...@@ -28,5 +38,3 @@ class ApiError extends Error {
}); });
} }
} }
module.exports = { ApiError };
...@@ -3,9 +3,9 @@ import { prettyJson, stackToArray, IDetailedError } from './common'; ...@@ -3,9 +3,9 @@ import { prettyJson, stackToArray, IDetailedError } from './common';
export class UserFriendlyError extends Error implements IDetailedError { export class UserFriendlyError extends Error implements IDetailedError {
originalError: Error; originalError: Error;
additionalInfo: string; additionalInfo?: string;
constructor(message: string, originalError: Error, additionalInfo: string) { constructor(message: string, originalError: Error, additionalInfo?: string) {
super(message); super(message);
this.originalError = originalError; this.originalError = originalError;
this.additionalInfo = additionalInfo; this.additionalInfo = additionalInfo;
......
...@@ -2,7 +2,6 @@ const vscode = require('vscode'); ...@@ -2,7 +2,6 @@ const vscode = require('vscode');
const request = require('request-promise'); const request = require('request-promise');
const fs = require('fs'); const fs = require('fs');
const { tokenService } = require('./services/token_service'); const { tokenService } = require('./services/token_service');
const statusBar = require('./status_bar');
const { UserFriendlyError } = require('./errors/user_friendly_error'); const { UserFriendlyError } = require('./errors/user_friendly_error');
const { ApiError } = require('./errors/api_error'); const { ApiError } = require('./errors/api_error');
const { getCurrentWorkspaceFolder } = require('./services/workspace_service'); const { getCurrentWorkspaceFolder } = require('./services/workspace_service');
...@@ -452,7 +451,6 @@ async function handlePipelineAction(action, workspaceFolder) { ...@@ -452,7 +451,6 @@ async function handlePipelineAction(action, workspaceFolder) {
if (pipeline && project) { if (pipeline && project) {
let endpoint = `/projects/${project.id}/pipelines/${pipeline.id}/${action}`; let endpoint = `/projects/${project.id}/pipelines/${pipeline.id}/${action}`;
let newPipeline = null;
if (action === 'create') { if (action === 'create') {
const branchName = await createGitService(workspaceFolder).fetchTrackingBranchName(); const branchName = await createGitService(workspaceFolder).fetchTrackingBranchName();
...@@ -461,16 +459,13 @@ async function handlePipelineAction(action, workspaceFolder) { ...@@ -461,16 +459,13 @@ async function handlePipelineAction(action, workspaceFolder) {
try { try {
const { response } = await fetch(endpoint, 'POST'); const { response } = await fetch(endpoint, 'POST');
newPipeline = response; return response;
} catch (e) { } catch (e) {
handleError(new UserFriendlyError(`Failed to ${action} pipeline.`, e)); throw new UserFriendlyError(`Failed to ${action} pipeline.`, e);
}
if (newPipeline) {
statusBar.refreshPipeline();
} }
} else { } else {
vscode.window.showErrorMessage('GitLab Workflow: No project or pipeline found.'); vscode.window.showErrorMessage('GitLab Workflow: No project or pipeline found.');
return undefined;
} }
} }
......
const vscode = require('vscode'); const vscode = require('vscode');
const gitLabService = require('./gitlab_service'); const gitLabService = require('./gitlab_service');
const openers = require('./openers'); const openers = require('./openers');
const statusBar = require('./status_bar');
const { getCurrentWorkspaceFolderOrSelectOne } = require('./services/workspace_service'); const { getCurrentWorkspaceFolderOrSelectOne } = require('./services/workspace_service');
async function showPicker() { async function showPicker() {
...@@ -33,7 +34,8 @@ async function showPicker() { ...@@ -33,7 +34,8 @@ async function showPicker() {
return; return;
} }
gitLabService.handlePipelineAction(selected.action, workspaceFolder); const newPipeline = await gitLabService.handlePipelineAction(selected.action, workspaceFolder);
if (newPipeline) statusBar.refreshPipeline();
} }
} }
......
...@@ -2,12 +2,7 @@ import * as temp from 'temp'; ...@@ -2,12 +2,7 @@ import * as temp from 'temp';
import simpleGit from 'simple-git'; import simpleGit from 'simple-git';
import * as path from 'path'; import * as path from 'path';
import * as fs from 'fs'; import * as fs from 'fs';
import { REMOTE } from './integration/test_infrastructure/constants'; import { REMOTE, DEFAULT_VS_CODE_SETTINGS } from './integration/test_infrastructure/constants';
const vsCodeSettings = {
'gitlab.instanceUrl': 'https://test.gitlab.com',
'files.enableTrash': false,
};
async function createTempFolder(): Promise<string> { async function createTempFolder(): Promise<string> {
return new Promise<string>((resolve, reject) => { return new Promise<string>((resolve, reject) => {
...@@ -34,6 +29,6 @@ export default async function createTmpWorkspace(autoCleanUp = true): Promise<st ...@@ -34,6 +29,6 @@ export default async function createTmpWorkspace(autoCleanUp = true): Promise<st
const git = simpleGit(dirPath, { binary: 'git' }); const git = simpleGit(dirPath, { binary: 'git' });
await git.init(); await git.init();
await git.addRemote(REMOTE.NAME, REMOTE.URL); await git.addRemote(REMOTE.NAME, REMOTE.URL);
await addFile(dirPath, '/.vscode/settings.json', JSON.stringify(vsCodeSettings)); await addFile(dirPath, '/.vscode/settings.json', JSON.stringify(DEFAULT_VS_CODE_SETTINGS));
return dirPath; return dirPath;
} }
{
"id": 7237201,
"name": "John Doe",
"username": "johndoe",
"state": "active",
"avatar_url": "https://secure.gravatar.com/avatar/6042a9152ada74d",
"web_url": "https://gitlab.com/johndoe"
}
...@@ -2,9 +2,11 @@ const assert = require('assert'); ...@@ -2,9 +2,11 @@ const assert = require('assert');
const sinon = require('sinon'); const sinon = require('sinon');
const vscode = require('vscode'); const vscode = require('vscode');
const simpleGit = require('simple-git'); const simpleGit = require('simple-git');
const { graphql } = require('msw');
const { insertSnippet } = require('../../src/commands/insert_snippet'); const { insertSnippet } = require('../../src/commands/insert_snippet');
const { tokenService } = require('../../src/services/token_service'); const { tokenService } = require('../../src/services/token_service');
const getServer = require('./test_infrastructure/mock_server'); const snippetsResponse = require('./fixtures/graphql/snippets.json');
const { getServer, createTextEndpoint } = require('./test_infrastructure/mock_server');
const { GITLAB_URL, REMOTE } = require('./test_infrastructure/constants'); const { GITLAB_URL, REMOTE } = require('./test_infrastructure/constants');
const { const {
createAndOpenFile, createAndOpenFile,
...@@ -18,7 +20,21 @@ describe('Insert snippet', async () => { ...@@ -18,7 +20,21 @@ describe('Insert snippet', async () => {
const sandbox = sinon.createSandbox(); const sandbox = sinon.createSandbox();
before(async () => { before(async () => {
server = getServer(); server = getServer([
createTextEndpoint(
'/projects/278964/snippets/111/files/master/test.js/raw',
'snippet content',
),
createTextEndpoint(
'/projects/278964/snippets/222/files/master/test2.js/raw',
'second blob content',
),
graphql.query('GetSnippets', (req, res, ctx) => {
if (req.variables.projectPath === 'gitlab-org/gitlab')
return res(ctx.data(snippetsResponse));
return res(ctx.data({ project: null }));
}),
]);
await tokenService.setToken(GITLAB_URL, 'abcd-secret'); await tokenService.setToken(GITLAB_URL, 'abcd-secret');
}); });
......
...@@ -3,7 +3,9 @@ const sinon = require('sinon'); ...@@ -3,7 +3,9 @@ const sinon = require('sinon');
const vscode = require('vscode'); const vscode = require('vscode');
const statusBar = require('../../src/status_bar'); const statusBar = require('../../src/status_bar');
const { tokenService } = require('../../src/services/token_service'); const { tokenService } = require('../../src/services/token_service');
const getServer = require('./test_infrastructure/mock_server'); const pipelinesResponse = require('./fixtures/rest/pipelines.json');
const pipelineResponse = require('./fixtures/rest/pipeline.json');
const { getServer, createJsonEndpoint } = require('./test_infrastructure/mock_server');
const { GITLAB_URL } = require('./test_infrastructure/constants'); const { GITLAB_URL } = require('./test_infrastructure/constants');
describe('GitLab status bar', () => { describe('GitLab status bar', () => {
...@@ -18,7 +20,10 @@ describe('GitLab status bar', () => { ...@@ -18,7 +20,10 @@ describe('GitLab status bar', () => {
}; };
before(async () => { before(async () => {
server = getServer(); server = getServer([
createJsonEndpoint('/projects/278964/pipelines?ref=master', pipelinesResponse),
createJsonEndpoint('/projects/278964/pipelines/47', pipelineResponse),
]);
await tokenService.setToken(GITLAB_URL, 'abcd-secret'); await tokenService.setToken(GITLAB_URL, 'abcd-secret');
}); });
......
...@@ -4,3 +4,7 @@ export const REMOTE = { ...@@ -4,3 +4,7 @@ export const REMOTE = {
NAME: 'origin', NAME: 'origin',
URL: 'git@test.gitlab.com:gitlab-org/gitlab.git', URL: 'git@test.gitlab.com:gitlab-org/gitlab.git',
}; };
export const DEFAULT_VS_CODE_SETTINGS = {
'gitlab.instanceUrl': GITLAB_URL,
'files.enableTrash': false,
};
const vscode = require('vscode'); import * as vscode from 'vscode';
import { SinonSandbox } from 'sinon';
import * as fs from 'fs';
import * as assert from 'assert';
import { DEFAULT_VS_CODE_SETTINGS } from './constants';
const createAndOpenFile = async testFileUri => { export const createAndOpenFile = async (testFileUri: vscode.Uri) => {
const createFileEdit = new vscode.WorkspaceEdit(); const createFileEdit = new vscode.WorkspaceEdit();
createFileEdit.createFile(testFileUri); createFileEdit.createFile(testFileUri);
await vscode.workspace.applyEdit(createFileEdit); await vscode.workspace.applyEdit(createFileEdit);
await vscode.window.showTextDocument(testFileUri); await vscode.window.showTextDocument(testFileUri);
}; };
const closeAndDeleteFile = async testFileUri => { export const closeAndDeleteFile = async (testFileUri: vscode.Uri) => {
await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
const edit = new vscode.WorkspaceEdit(); const edit = new vscode.WorkspaceEdit();
edit.deleteFile(testFileUri); edit.deleteFile(testFileUri);
await vscode.workspace.applyEdit(edit); await vscode.workspace.applyEdit(edit);
}; };
const simulateQuickPickChoice = (sandbox, nthItem) => { export const simulateQuickPickChoice = (sandbox: SinonSandbox, nthItem: number) => {
sandbox.stub(vscode.window, 'showQuickPick').callsFake(async options => { sandbox.stub(vscode.window, 'showQuickPick').callsFake(async options => {
return options[nthItem]; return (await options)[nthItem];
}); });
}; };
module.exports = { createAndOpenFile, closeAndDeleteFile, simulateQuickPickChoice }; export const getWorkspaceFoder = () => {
const folders = vscode.workspace.workspaceFolders;
return folders && folders[0]?.uri.fsPath;
};
const { setupServer } = require('msw/node'); const { setupServer } = require('msw/node');
const { rest, graphql } = require('msw'); const { rest } = require('msw');
const { API_URL_PREFIX } = require('./constants'); const { API_URL_PREFIX } = require('./constants');
const projectResponse = require('../fixtures/rest/project.json'); const projectResponse = require('../fixtures/rest/project.json');
const versionResponse = require('../fixtures/rest/version.json'); const versionResponse = require('../fixtures/rest/version.json');
const openIssueResponse = require('../fixtures/rest/open_issue.json');
const openMergeRequestResponse = require('../fixtures/rest/open_mr.json');
const pipelinesResponse = require('../fixtures/rest/pipelines.json');
const pipelineResponse = require('../fixtures/rest/pipeline.json');
const snippetsResponse = require('../fixtures/graphql/snippets.json');
const createJsonEndpoint = (path, response) => const createJsonEndpoint = (path, response) =>
rest.get(`${API_URL_PREFIX}${path}`, (req, res, ctx) => { rest.get(`${API_URL_PREFIX}${path}`, (req, res, ctx) => {
return res(ctx.status(200), ctx.json(response)); return res(ctx.status(200), ctx.json(response));
}); });
const createQueryJsonEndpoint = (path, queryResponseMap) =>
rest.get(`${API_URL_PREFIX}${path}`, (req, res, ctx) => {
const response = queryResponseMap[req.url.search];
if (!response) {
console.warn(`API call ${req.url.toString()} doesn't have a query handler.`);
return res(ctx.status(404));
}
return res(ctx.status(200), ctx.json(response));
});
const createTextEndpoint = (path, response) => const createTextEndpoint = (path, response) =>
rest.get(`${API_URL_PREFIX}${path}`, (req, res, ctx) => { rest.get(`${API_URL_PREFIX}${path}`, (req, res, ctx) => {
return res(ctx.status(200), ctx.text(response)); return res(ctx.status(200), ctx.text(response));
...@@ -21,29 +26,15 @@ const createTextEndpoint = (path, response) => ...@@ -21,29 +26,15 @@ const createTextEndpoint = (path, response) =>
const notFoundByDefault = rest.get(/.*/, (req, res, ctx) => res(ctx.status(404))); const notFoundByDefault = rest.get(/.*/, (req, res, ctx) => res(ctx.status(404)));
module.exports = () => { const getServer = (handlers = []) => {
const server = setupServer( const server = setupServer(
createJsonEndpoint('/projects/gitlab-org%2Fgitlab', projectResponse), createJsonEndpoint('/projects/gitlab-org%2Fgitlab', projectResponse),
createJsonEndpoint('/version', versionResponse), createJsonEndpoint('/version', versionResponse),
createJsonEndpoint('/projects/278964/merge_requests?scope=assigned_to_me&state=opened', [ ...handlers,
openMergeRequestResponse,
]),
createJsonEndpoint('/projects/278964/issues?scope=assigned_to_me&state=opened', [
openIssueResponse,
]),
createJsonEndpoint('/projects/278964/pipelines?ref=master', pipelinesResponse),
createJsonEndpoint('/projects/278964/pipelines/47', pipelineResponse),
createTextEndpoint('/projects/278964/snippets/111/files/master/test.js/raw', 'snippet content'),
createTextEndpoint(
'/projects/278964/snippets/222/files/master/test2.js/raw',
'second blob content',
),
graphql.query('GetSnippets', (req, res, ctx) => {
if (req.variables.projectPath === 'gitlab-org/gitlab') return res(ctx.data(snippetsResponse));
return res(ctx.data({ project: null }));
}),
notFoundByDefault, notFoundByDefault,
); );
server.listen(); server.listen();
return server; return server;
}; };
module.exports = { getServer, createJsonEndpoint, createQueryJsonEndpoint, createTextEndpoint };
const assert = require('assert'); const assert = require('assert');
const vscode = require('vscode');
const IssuableDataProvider = require('../../src/data_providers/issuable').DataProvider; const IssuableDataProvider = require('../../src/data_providers/issuable').DataProvider;
const { tokenService } = require('../../src/services/token_service'); const { tokenService } = require('../../src/services/token_service');
const getServer = require('./test_infrastructure/mock_server'); const openIssueResponse = require('./fixtures/rest/open_issue.json');
const openMergeRequestResponse = require('./fixtures/rest/open_mr.json');
const userResponse = require('./fixtures/rest/user.json');
const { getServer, createQueryJsonEndpoint } = require('./test_infrastructure/mock_server');
const { GITLAB_URL } = require('./test_infrastructure/constants'); const { GITLAB_URL } = require('./test_infrastructure/constants');
describe('GitLab tree view', () => { describe('GitLab tree view', () => {
let server; let server;
let dataProvider; let dataProvider;
const customQuerySettings = [
{
name: 'Issues assigned to me',
type: 'issues',
scope: 'assigned_to_me',
state: 'opened',
noItemText: 'There is no issue assigned to you.',
},
{
name: 'Merge requests assigned to me',
type: 'merge_requests',
scope: 'assigned_to_me',
state: 'opened',
noItemText: 'There is no MR assigned to you.',
},
{
name: 'Custom GitLab Query for MR',
type: 'merge_requests',
scope: 'assigned_to_me',
state: 'all',
noItemText: 'There is no MR assigned to you.',
maxResults: 30,
labels: ['frontend', 'backend'],
milestone: '13.6',
author: 'johndoe',
assignee: 'johndoe',
search: 'query',
createdBefore: '2020-10-11T03:45:40Z',
createdAfter: '2018-11-01T03:45:40Z',
updatedBefore: '2020-10-30T03:45:40Z',
updatedAfter: '2018-11-01T03:45:40Z',
wip: 'yes',
orderBy: 'updated_at',
sort: 'asc',
},
{
name: 'Custom GitLab Query for issues',
type: 'issues',
scope: 'assigned_to_me',
state: 'opened',
noItemText: 'There is no Issue assigned to you.',
confidential: true,
excludeLabels: ['backstage'],
excludeMilestone: ['13.5'],
excludeAuthor: 'johndoe',
excludeAssignee: 'johndoe',
excludeSearch: 'bug',
excludeSearchIn: 'description',
},
];
before(async () => { before(async () => {
server = getServer(); server = getServer([
createQueryJsonEndpoint('/users', {
'?username=johndoe': [userResponse],
}),
createQueryJsonEndpoint('/projects/278964/merge_requests', {
'?scope=assigned_to_me&state=opened': [openMergeRequestResponse],
'?scope=assigned_to_me&state=all&labels=frontend,backend&milestone=13.6&author_id=7237201&assignee_id=7237201&search=query&created_before=2020-10-11T03&created_after=2018-11-01T03&updated_before=2020-10-30T03&updated_after=2018-11-01T03&wip=yes&order_by=updated_at&sort=asc&per_page=30': [
{ ...openMergeRequestResponse, title: 'Custom Query MR' },
],
}),
createQueryJsonEndpoint('/projects/278964/issues', {
'?scope=assigned_to_me&state=opened': [openIssueResponse],
'?scope=assigned_to_me&state=opened&confidential=true&not[labels]=backstage&not[milestone]=13.5&not[author_username]=johndoe&not[assignee_username]=johndoe&not[search]=bug&not[in]=description': [
{ ...openIssueResponse, title: 'Custom Query Issue' },
],
}),
]);
await tokenService.setToken(GITLAB_URL, 'abcd-secret'); await tokenService.setToken(GITLAB_URL, 'abcd-secret');
await vscode.workspace.getConfiguration().update('gitlab.customQueries', customQuerySettings);
}); });
beforeEach(() => { beforeEach(() => {
...@@ -21,6 +93,7 @@ describe('GitLab tree view', () => { ...@@ -21,6 +93,7 @@ describe('GitLab tree view', () => {
after(async () => { after(async () => {
server.close(); server.close();
await tokenService.setToken(GITLAB_URL, undefined); await tokenService.setToken(GITLAB_URL, undefined);
await vscode.workspace.getConfiguration().update('gitlab.customQueries', undefined);
}); });
/** /**
...@@ -31,7 +104,7 @@ describe('GitLab tree view', () => { ...@@ -31,7 +104,7 @@ describe('GitLab tree view', () => {
const [chosenCategory] = categories.filter(c => c.label === label); const [chosenCategory] = categories.filter(c => c.label === label);
assert( assert(
chosenCategory, chosenCategory,
`Can't open category ${label} because it's not present in ${categories}`, `Can't open category ${label} because it's not present in ${categories.map(c => c.label)}`,
); );
return await dataProvider.getChildren(chosenCategory); return await dataProvider.getChildren(chosenCategory);
} }
...@@ -55,4 +128,18 @@ describe('GitLab tree view', () => { ...@@ -55,4 +128,18 @@ describe('GitLab tree view', () => {
'!33824 · Web IDE - remove unused actions (mappings)', '!33824 · Web IDE - remove unused actions (mappings)',
); );
}); });
it('handles full custom query for MR', async () => {
const customMergeRequests = await openCategory('Custom GitLab Query for MR');
assert.strictEqual(customMergeRequests.length, 1);
assert.strictEqual(customMergeRequests[0].label, '!33824 · Custom Query MR');
});
it('handles full custom query for issues', async () => {
const customMergeRequests = await openCategory('Custom GitLab Query for issues');
assert.strictEqual(customMergeRequests.length, 1);
assert.strictEqual(customMergeRequests[0].label, '#219925 · Custom Query Issue');
});
}); });
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册