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

chore: add tests to template automation and clean up templating logic #2052

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@ async function closeIssue(github, owner, repo, number) {
owner: owner,
repo: repo,
issue_number: number,
body: 'Issue was opened with an invalid reproduction link. Please make sure the repository is a valid, publicly-accessible github repository, and make sure the url is complete (example: https://github.com/googleapis/google-cloud-node)'
body: "Issue was opened with an invalid reproduction link. Please make sure the repository is a valid, publicly-accessible github repository, and make sure the url is complete (example: https://github.com/googleapis/google-cloud-node)"
});
await github.rest.issues.update({
owner: owner,
repo: repo,
issue_number: number,
state: 'closed'
state: "closed"
});
}
module.exports = async ({github, context}) => {
module.exports = async ({ github, context }) => {
const owner = context.repo.owner;
const repo = context.repo.repo;
const number = context.issue.number;
Expand All @@ -37,18 +37,23 @@ module.exports = async ({github, context}) => {
issue_number: number,
});

const isBugTemplate = issue.data.body.includes('Link to the code that reproduces this issue');
const isBugTemplate = issue.data.body.includes("Link to the code that reproduces this issue");

if (isBugTemplate) {
console.log(`Issue ${number} is a bug template`)
try {
const link = issue.data.body.split('\n')[18].match(/(https?:\/\/(gist\.)?github.com\/.*)/)[0];
console.log(`Issue ${number} contains this link: ${link}`)
const isValidLink = (await fetch(link)).ok;
console.log(`Issue ${number} has a ${isValidLink ? 'valid' : 'invalid'} link`)
if (!isValidLink) {
await closeIssue(github, owner, repo, number);
}
const text = issue.data.body;
const match = text.match(/Link to the code that reproduces this issue. A link to a \*\*public\*\* Github Repository with a minimal reproduction/);
if (match) {
const nextLineIndex = text.indexOf('http', match.index);
const link = text.substring(nextLineIndex, text.indexOf('\n', nextLineIndex));
console.log(`Issue ${number} contains this link: ${link}`);
const isValidLink = (await fetch(link)).ok;
console.log(`Issue ${number} has a ${isValidLink ? "valid" : "invalid"} link`)
if (!isValidLink) {
await closeIssue(github, owner, repo, number);
}
}
} catch (err) {
await closeIssue(github, owner, repo, number);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

'use strict';

const {describe, it} = require('mocha');
const closeInvalidLink = require('./close-invalid-link.cjs');
const fs = require('fs');

describe('Quickstart', () => {
it('does not do anything if it is not a bug', async () => {
const context = {repo: {owner: 'testOrg', repo: 'testRepo'}, issue: {number: 1}}
const octokit = {rest: {issues: {get: () => {return {data: {body: "I'm having a problem with this."}}}}}};
await closeInvalidLink({github: octokit, context});
});

it('does not do anything if it is a bug with an appropriate link', async () => {
const context = {repo: {owner: 'testOrg', repo: 'testRepo'}, issue: {number: 1}}
const octokit = {rest: {issues: {get: () => {return {data: {body: fs.readFileSync('./fixtures/validIssueBody.txt', 'utf-8')}}}}}};
await closeInvalidLink({github: octokit, context});
});

it('does not do anything if it is a bug with an appropriate link and the template changes', async () => {
const context = {repo: {owner: 'testOrg', repo: 'testRepo'}, issue: {number: 1}}
const octokit = {rest: {issues: {get: () => {return {data: {body: fs.readFileSync('./fixtures/validIssueBodyDifferentLinkLocation.txt', 'utf-8')}}}}}};
await closeInvalidLink({github: octokit, context});
});

it('closes the issue if the link is invalid', async () => {
const context = {repo: {owner: 'testOrg', repo: 'testRepo'}, issue: {number: 1}}
const octokit = {rest: {issues: {get: () => {return {data: {body: fs.readFileSync('./fixtures/invalidIssueBody.txt', 'utf-8')}}}, createComment: () => {return}, update: () => {return}}}};
await closeInvalidLink({github: octokit, context});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

'use strict';

const {describe, it} = require('mocha');
const removeResponseLabel = require('./remove-response-label.cjs');
const closeUnresponsive = require('./close-unresponsive.cjs');

function getISODateDaysAgo(days) {
const today = new Date();
const daysAgo = new Date(today.setDate(today.getDate() - days));
return daysAgo.toISOString();
}

describe('Quickstart', () => {
it('closes the issue if the OP has not responded within the allotted time and there is a needs-more-info label', async () => {
const context = {owner: 'testOrg', repo: 'testRepo'}
const issuesInRepo = [{user: {login: 'OP'}, labels: [{name: 'needs more info'}]}]
const eventsInIssue = [{event: 'labeled', label: {name: 'needs more info'}, created_at: getISODateDaysAgo(16)}];
const octokit = {rest: {issues: {listForRepo: () => {return {data: issuesInRepo}}, update: () => {return}, createComment: () => {return}}}, paginate: () => {return eventsInIssue}};
await closeUnresponsive({github: octokit, context});
});

it('does nothing if not enough time has passed and there is a needs-more-info label', async () => {
const context = {owner: 'testOrg', repo: 'testRepo'}
const issuesInRepo = [{user: {login: 'OP'}, labels: [{name: 'needs more info'}]}]
const eventsInIssue = [{event: 'labeled', label: {name: 'needs more info'}, created_at: getISODateDaysAgo(14)}];
const octokit = {rest: {issues: {listForRepo: () => {return {data: issuesInRepo}}}}, paginate: () => {return eventsInIssue}};
await closeUnresponsive({github: octokit, context});
});

it('removes the label if OP responded', async () => {
const context = {actor: 'OP', repo: {owner: 'testOrg', repo: 'testRepo'}, issue: {number: 1}};
const issueContext = {user: 'OP', login: 'OP', labels: [{name: 'needs more info'}]};
const octokit = {rest: {issues: {get: () => {return {data: issueContext}}, removeLabel: () => {return}}}};
await removeResponseLabel({github: octokit, context});
});

it('does not remove the label if author responded', async () => {
const context = {actor: 'repo-maintainer', repo: {owner: 'testOrg', repo: 'testRepo'}, issue: {number: 1}};
const issueContext = {user: 'OP', login: 'OP', labels: [{name: 'needs more info'}]};
const octokit = {rest: {issues: {get: () => {return {data: issueContext}}}}};
await removeResponseLabel({github: octokit, context});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,57 +13,57 @@
// limitations under the License.

function labeledEvent(data) {
return data.event === 'labeled' && data.label.name === 'needs more info';
}

const numberOfDaysLimit = 15;
const close_message = `This has been closed since a request for information has \
not been answered for ${numberOfDaysLimit} days. It can be reopened when the \
requested information is provided.`;

module.exports = async ({github, context}) => {
const owner = context.repo.owner;
const repo = context.repo.repo;

const issues = await github.rest.issues.listForRepo({
owner: owner,
repo: repo,
labels: 'needs more info',
});
const numbers = issues.data.map((e) => e.number);

for (const number of numbers) {
const events = await github.paginate(
github.rest.issues.listEventsForTimeline,
{
owner: owner,
repo: repo,
issue_number: number,
},
(response) => response.data.filter(labeledEvent)
);

const latest_response_label = events[events.length - 1];

const created_at = new Date(latest_response_label.created_at);
const now = new Date();
const diff = now - created_at;
const diffDays = diff / (1000 * 60 * 60 * 24);

if (diffDays > numberOfDaysLimit) {
await github.rest.issues.update({
owner: owner,
repo: repo,
issue_number: number,
state: 'closed',
});

await github.rest.issues.createComment({
owner: owner,
repo: repo,
issue_number: number,
body: close_message,
});
}
return data.event === "labeled" && data.label.name === "needs more info";
}

const numberOfDaysLimit = 15;
const close_message = `This has been closed since a request for information has \
not been answered for ${numberOfDaysLimit} days. It can be reopened when the \
requested information is provided.`;

module.exports = async ({ github, context }) => {
const owner = context.repo.owner;
const repo = context.repo.repo;

const issues = await github.rest.issues.listForRepo({
owner: owner,
repo: repo,
labels: "needs more info",
});
const numbers = issues.data.map((e) => e.number);

for (const number of numbers) {
const events = await github.paginate(
github.rest.issues.listEventsForTimeline,
{
owner: owner,
repo: repo,
issue_number: number,
},
(response) => response.data.filter(labeledEvent)
);

const latest_response_label = events[events.length - 1];

const created_at = new Date(latest_response_label.created_at);
const now = new Date();
const diff = now - created_at;
const diffDays = diff / (1000 * 60 * 60 * 24);

if (diffDays > numberOfDaysLimit) {
await github.rest.issues.update({
owner: owner,
repo: repo,
issue_number: number,
state: "closed",
});

await github.rest.issues.createComment({
owner: owner,
repo: repo,
issue_number: number,
body: close_message,
});
}
};
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "tests",
"private": true,
"description": "tests for script",
"scripts": {
"test": "mocha close-invalid-link.test.cjs && mocha close-or-remove-response-label.test.cjs"
},
"author": "Google Inc.",
"license": "Apache-2.0",
"engines": {
"node": ">=18"
},
"devDependencies": {
"@octokit/rest": "^21.0.2",
"mocha": "^11.0.1"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@
// limitations under the License.

module.exports = async ({ github, context }) => {
const commenter = context.actor;
const issue = await github.rest.issues.get({
const commenter = context.actor;
const issue = await github.rest.issues.get({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const author = issue.data.user.login;
const labels = issue.data.labels.map((e) => e.name);

if (author === commenter && labels.includes("needs more info")) {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
name: "needs more info",
});
const author = issue.data.user.login;
const labels = issue.data.labels.map((e) => e.name);

if (author === commenter && labels.includes('needs more info')) {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
name: 'needs more info',
});
}
};
}
};
14 changes: 14 additions & 0 deletions synthtool/gcp/templates/node_library/.github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ jobs:
- run: npm test
env:
MOCHA_THROW_DEPRECATION: false
test-script:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- run: node --version
- run: npm install --engine-strict
- run: npm test
env:
MOCHA_THROW_DEPRECATION: false
windows:
runs-on: windows-latest
steps:
Expand All @@ -34,7 +46,9 @@ jobs:
with:
node-version: 18
- run: npm install --engine-strict
working-directory: .github/scripts
- run: npm test
working-directory: .github/scripts
env:
MOCHA_THROW_DEPRECATION: false
lint:
Expand Down
Loading