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

Iss3 #5

Merged
merged 14 commits into from
Oct 14, 2022
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
10 changes: 9 additions & 1 deletion .deepsource.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
version = 1

test_patterns = ["*.test.js"]

exclude_patterns = ["**/node_modules/**"]

[[analyzers]]
name = "test-coverage"
enabled = true

[[analyzers]]
name = "javascript"
enabled = true
enabled = false
24 changes: 24 additions & 0 deletions .github/workflows/ci-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Run Unit Tests

on:
pull_request:
types: [opened, reopened]
push:
branches:
- '*'

jobs:
test:
runs-on: ubuntu-latest
name: Run tests
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Report results to DeepSource
run: |
npm test
curl https://deepsource.io/cli | sh
./bin/deepsource report --analyzer test-coverage --key javascript --value-file ./coverage/cobertura-coverage.xml
env:
DEEPSOURCE_DSN: ${{ secrets.DEEPSOURCE_DSN }}
33 changes: 24 additions & 9 deletions gitsync.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@ const core = require('@actions/core');
const github = require('@actions/github');
const azdo = require('azure-devops-node-api');
const showdown = require('showdown');
showdown.setFlavor('github');

const JSDOM = require('jsdom').JSDOM;
;(globalThis).window = new JSDOM('', {}).window;

module.exports = class GitSync {

constructor(level = "silent") {
log.setLevel(level, true);
}


// skipcq: TCV-001
async run() {
try {
const context = github.context;
Expand All @@ -18,7 +24,13 @@ module.exports = class GitSync {
let config = this.getConfig(context.payload, env);
log.debug(config);

let workItem = await this.performWork(config);
// Temporary fix until support of PRs
if (config.issue.nodeId.startsWith("PR_")) {
// Log and skip PRs (comments)
log.info(`Action is performed on PR #${config.issue.number}. Skipping...`);
} else {
await this.performWork(config);
}
} catch (exc) {
log.error(exc);
}
Expand Down Expand Up @@ -625,7 +637,7 @@ module.exports = class GitSync {
result = await client.queryByWiql(wiql, context);
log.debug("Query results:", result);

if (result == null) {
if (result === null) {
log.error("Error: project name appears to be invalid.");
core.setFailed("Error: project name appears to be invalid.");
return -1;
Expand All @@ -641,17 +653,19 @@ module.exports = class GitSync {
workItems.forEach(async (workItem) => { await this.updateIssue(config, client, workItem); });
}

async updateIssue(config, client, workItem) {
updateIssue(config, client, workItem) {
log.info(`Updating issue for work item (${workItem.id})...`);
const octokit = new github.getOctokit(config.github.token);
const owner = config.GITHUB_REPOSITORY_OWNER;
const repo = config.GITHUB_REPOSITORY.replace(owner + "/", "");
const converter = new showdown.Converter();

log.debug(`[WORKITEM: ${workItem.id}] Owner:`, owner);
log.debug(`[WORKITEM: ${workItem.id}] Repo:`, repo);

return client.getWorkItem(workItem.id, ["System.Title", "System.Description", "System.State", "System.ChangedDate"]).then(async (wiObj) => {
let parsed = wiObj.fields["System.Title"].match(/^GH\s#(\d+):\s(.*)/);

let issue_number = parsed[1];
log.debug(`[WORKITEM: ${workItem.id} / ISSUE: ${issue_number}] Issue Number:`, issue_number);

Expand All @@ -672,17 +686,18 @@ module.exports = class GitSync {
if (new Date(wiObj.fields["System.ChangedDate"]) > new Date(issue.updated_at)) {
log.debug(`[WORKITEM: ${workItem.id} / ISSUE: ${issue_number}] WorkItem.ChangedDate (${new Date(wiObj.fields["System.ChangedDate"])}) is more recent than Issue.UpdatedAt (${new Date(issue.updated_at)}). Updating issue...`);
let title = parsed[2];
let body = wiObj.fields["System.Description"];
let body = converter.makeMarkdown(wiObj.fields["System.Description"]).replace(/<br>/g, "").trim();

let states = config.ado.states;
let state = Object.keys(states).find(k => states[k]==wiObj.fields["System.State"]);
let state = Object.keys(states).find(k => states[k]===wiObj.fields["System.State"]);

log.debug(`[WORKITEM: ${workItem.id} / ISSUE: ${issue_number}] Title:`, title);
log.debug(`[WORKITEM: ${workItem.id} / ISSUE: ${issue_number}] Body:`, body);
log.debug(`[WORKITEM: ${workItem.id} / ISSUE: ${issue_number}] State:`, state);

if (title != issue.title ||
body != issue.body ||
state != issue.state) {
if (title !== issue.title ||
body !== issue.body ||
state !== issue.state) {

let result = await octokit.rest.issues.update({
owner,
Expand Down
159 changes: 157 additions & 2 deletions gitsync.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ const chai = require('chai');
var sinon = require('sinon');
const assert = chai.assert;
chai.use(require('sinon-chai'));
const proxyquire = require('proxyquire');
const proxyquire = require('proxyquire').noPreserveCache();
const { DateTime } = require('luxon');
const decache = require('decache');

const GitSync = require('./gitsync');

Expand Down Expand Up @@ -2355,6 +2356,8 @@ describe("index", () => {
state: "new"
});
sinon.restore();
decache("./mocks/workItem.json");
decache("./mocks/githubIssue.json");
});

it("should update github issue when AzDO change date is newer than github change date and body is different", async () => {
Expand Down Expand Up @@ -2426,6 +2429,8 @@ describe("index", () => {
state: "new"
});
sinon.restore();
decache("./mocks/workItem.json");
decache("./mocks/githubIssue.json");
});

it("should update github issue when AzDO change date is newer than github change date and state is different", async () => {
Expand Down Expand Up @@ -2499,11 +2504,157 @@ describe("index", () => {
state: "closed"
});
sinon.restore();
decache("./mocks/workItem.json");
decache("./mocks/githubIssue.json");
});

it("should successfully convert html code blocks back to markdown and not update github issue when equal", async () => {
let workItem = require("./mocks/workItemCode.json");
workItem.fields["System.Description"] = "testing<br />" + workItem.fields["System.Description"];
workItem.fields["System.Title"] = "GH #12: Testing title";
let gitHubIssue = require("./mocks/githubIssue.json");
gitHubIssue.data.updated_at = DateTime.fromJSDate(new Date(workItem.fields["System.ChangedDate"])).minus({ days: 5}).toJSDate();
gitHubIssue.data.body = "testing\n\n<pre>public class Foo() {\n var number = 0;\n var text = \"Hello World!\";\n return 0;\n}</pre>";

const stubbedCore = sinon.stub().callsFake();
const stubbedWorkItemTrackingApi = {
getWorkItem: sinon.stub().resolves(workItem)
}
const stubbedIssues = {
get: sinon.stub().resolves(gitHubIssue),
update: sinon.stub().resolves()
}
const proxiedGitSync = proxyquire('./gitsync', {
"@actions/github": {
getOctokit: sinon.stub().returns({
rest: {
issues: stubbedIssues
}
})
},
"@actions/core": {
setFailed: stubbedCore
}
});

let now = Date.now();
let config = {
log_level: 'silent',
ado: {
states: { reopened: "New" },
project: "foo",
bypassRules: true,
wit: "User Story",
states: {
new: "New",
closed: "Closed",
reopened: "New",
deleted: "Removed",
active: "Active"
},
},
closed_at: now,
repository: {
full_name: "foo/bar"
},
label: {
name: "baz"
},
github: {
token: "blah blah"
},
GITHUB_REPOSITORY: "foo/bar",
GITHUB_REPOSITORY_OWNER: "foo"
};

var sync = new proxiedGitSync();
var result = await sync.updateIssue(config, stubbedWorkItemTrackingApi, workItem);

assert.isNull(result);
sinon.assert.notCalled(stubbedIssues.update);
sinon.restore();
decache("./mocks/workItemCode.json");
decache("./mocks/githubIssue.json");
});

it("should successfully convert html code blocks back to markdown and update github issue when not equal", async () => {
let workItem = require("./mocks/workItemCode.json");
workItem.fields["System.Description"] = "testing<br \>some more<br \>" + workItem.fields["System.Description"];
workItem.fields["System.Title"] = "GH #12: Testing title";
let gitHubIssue = require("./mocks/githubIssue.json");
gitHubIssue.data.updated_at = DateTime.fromJSDate(new Date(workItem.fields["System.ChangedDate"])).minus({ days: 5}).toJSDate();
gitHubIssue.data.body = "testing\n<pre>public class Foo() {\n var number = 0;\n var text = \"Hello World!\";\n return 0;\n}</pre>";

const stubbedCore = sinon.stub().callsFake();
const stubbedWorkItemTrackingApi = {
getWorkItem: sinon.stub().resolves(workItem)
}
const stubbedIssues = {
get: sinon.stub().resolves(gitHubIssue),
update: sinon.stub().resolves()
}
const proxiedGitSync = proxyquire('./gitsync', {
"@actions/github": {
getOctokit: sinon.stub().returns({
rest: {
issues: stubbedIssues
}
})
},
"@actions/core": {
setFailed: stubbedCore
}
});

let now = Date.now();
let config = {
log_level: 'silent',
ado: {
states: { reopened: "New" },
project: "foo",
bypassRules: true,
wit: "User Story",
states: {
new: "New",
closed: "Closed",
reopened: "New",
deleted: "Removed",
active: "Active"
},
},
closed_at: now,
repository: {
full_name: "foo/bar"
},
label: {
name: "baz"
},
github: {
token: "blah blah"
},
GITHUB_REPOSITORY: "foo/bar",
GITHUB_REPOSITORY_OWNER: "foo"
};

var sync = new proxiedGitSync();
await sync.updateIssue(config, stubbedWorkItemTrackingApi, workItem);

sinon.assert.calledWith(stubbedIssues.update, {
owner: "foo",
repo: "bar",
issue_number: '12',
title: "Testing title",
body: "testing\n\nsome more\n\n<pre>public class Foo() {\n var number = 0;\n var text = \"Hello World!\";\n return 0;\n}</pre>",
state: "new"
});
sinon.restore();
decache("./mocks/workItemCode.json");
decache("./mocks/githubIssue.json");
});

it("should not update github issue when AzDO change date is newer than github change date but data is the same", async () => {
let workItem = require("./mocks/workItem.json");
workItem.fields["System.Title"] = "GH #12: Testing title";
workItem.fields["System.Title"] = "GH #14: Testing title";
workItem.fields["System.Description"] = "Some body";
workItem.fields["System.State"] = "New";
let gitHubIssue = require("./mocks/githubIssue.json");
Expand Down Expand Up @@ -2566,6 +2717,8 @@ describe("index", () => {
assert.isNull(result);
sinon.assert.notCalled(stubbedIssues.update);
sinon.restore();
decache("./mocks/workItem.json");
decache("./mocks/githubIssue.json");
});

it("should not update github issue when AzDO change date is newer than github change date", async () => {
Expand Down Expand Up @@ -2629,6 +2782,8 @@ describe("index", () => {

assert.isNull(result);
sinon.restore();
decache("./mocks/workItem.json");
decache("./mocks/githubIssue.json");
});
});
});
11 changes: 11 additions & 0 deletions mocks/workItemCode.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"id": 1,
"fields": {
"System.Title": "GH #12: title 1",
"System.Description": "<pre>public class Foo() {\n var number = 0;\n var text = &quot;Hello World!&quot;;\n return 0;\n}</pre>",
"System.WorkItemType": "User Story",
"System.State": "New",
"System.Tags": "GitHub Issue;GitHub Repo: foo/bar;GitHub Label: fiz;GitHub Label: baz;",
"System.ChangedDate": "2020-01-01"
}
}
1 change: 1 addition & 0 deletions node_modules/.bin/acorn

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions node_modules/.bin/escodegen

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions node_modules/.bin/esgenerate

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading