diff --git a/README.md b/README.md index 5292aa04..ee3dfcf6 100644 --- a/README.md +++ b/README.md @@ -49,34 +49,69 @@ Instead, you can make use of this GitHub Action from the comfort of your own rep 1. [Create a Twitter app](docs/01-create-twitter-app.md) with your shared Twitter account and store the credentials as `TWITTER_API_KEY`, `TWITTER_API_SECRET_KEY`, `TWITTER_ACCESS_TOKEN` and `TWITTER_ACCESS_TOKEN_SECRET` in your repository’s secrets settings. 2. [Create a `.github/workflows/twitter-together.yml` file](docs/02-create-twitter-together-workflow.md) with the content below. Make sure to replace `'main'` if you changed your repository's default branch. - ```yml - on: [push, pull_request] - name: Twitter, together! - jobs: - preview: - name: Preview - runs-on: ubuntu-latest - if: github.event_name == 'pull_request' - steps: - - uses: twitter-together/action@v2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - tweet: - name: Tweet - runs-on: ubuntu-latest - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - steps: - - name: checkout main - uses: actions/checkout@v3 - - name: Tweet - uses: twitter-together/action@v2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }} - TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} - TWITTER_API_KEY: ${{ secrets.TWITTER_API_KEY }} - TWITTER_API_SECRET_KEY: ${{ secrets.TWITTER_API_SECRET_KEY }} - ``` +```yml +on: [push, pull_request] +name: Twitter, together! +jobs: + preview: + name: Preview + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - name: checkout pull request + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} + - name: Validate Tweets + uses: twitter-together/action@v3 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + tweet: + name: Tweet + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + steps: + - name: checkout main + uses: actions/checkout@v3 + - name: Tweet + uses: twitter-together/action@v3 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }} + TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} + TWITTER_API_KEY: ${{ secrets.TWITTER_API_KEY }} + TWITTER_API_SECRET_KEY: ${{ secrets.TWITTER_API_SECRET_KEY }} +``` + +TODO: CONFIRM + +If you wish to have this action create preview comments in the PR thread, you can use the following config. + +Note that `pull_request_target` events have elevated permissions, so if you are using this config, you should configure your repository to only trigger actions that are trusted. You can do this in [various](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/) ways, including preventing outside contributors from triggering actions automatically, and requiring only allowing actions from Verified Creators in your repository Settings -> Actions -> General. + +You can also securely enable PR comments only for local branch commits, using the `pull_request` events with an `ENABLE_COMMENTS: 1` env variable, but comments will not be created for PRs from forks. + +```yml +# enable comments, but beware of security implications +on: [push, pull_request_target] +name: Twitter, together! +jobs: + preview: + name: Preview + runs-on: ubuntu-latest + if: github.event_name == 'pull_request_target' + permissions: + pull-requests: write + steps: + - name: checkout pull request + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} + - name: Validate Tweets + uses: twitter-together/action@v3 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +``` 3. After creating or updating `.github/workflows/twitter-together.yml` in your repository’s default branch, a pull request will be created with further instructions. diff --git a/docs/02-create-twitter-together-workflow.md b/docs/02-create-twitter-together-workflow.md index 5bf5662e..275bd0d5 100644 --- a/docs/02-create-twitter-together-workflow.md +++ b/docs/02-create-twitter-together-workflow.md @@ -21,7 +21,12 @@ jobs: runs-on: ubuntu-latest if: github.event_name == 'pull_request' steps: - - uses: twitter-together/action@v2 + - name: checkout pull request + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} + - name: Validate Tweets + uses: twitter-together/action@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} tweet: diff --git a/lib/common/parse-tweet-file-content.js b/lib/common/parse-tweet-file-content.js index 77b1f885..45eb0492 100644 --- a/lib/common/parse-tweet-file-content.js +++ b/lib/common/parse-tweet-file-content.js @@ -6,6 +6,7 @@ const { existsSync } = require("fs"); const { join } = require("path"); const { parseTweet } = require("twitter-text"); const { load } = require("js-yaml"); +const parseTweetId = require("./parse-tweet-id"); const OPTION_REGEX = /^\(\s?\)\s+/; const FRONT_MATTER_REGEX = new RegExp( @@ -87,6 +88,9 @@ function parseTweetFileContent(text, dir, isThread = false) { } function validateOptions(options, text, dir) { + if (options.retweet || options.reply) + parseTweetId(options.retweet || options.reply); + if (options.retweet && !text && options.poll) throw new Error("Cannot attach a poll to a retweet"); diff --git a/lib/common/parse-tweet-id.js b/lib/common/parse-tweet-id.js new file mode 100644 index 00000000..d33f4e41 --- /dev/null +++ b/lib/common/parse-tweet-id.js @@ -0,0 +1,16 @@ +module.exports = parseTweetId; + +const TWEET_REGEX = /^https:\/\/twitter\.com\/[^/]+\/status\/(\d+)$/; + +// TODO allow differently formatted URLs and tweet ids ? +// https://github.com/twitter-together/action/issues/221 + +// TODO: Should we check if the referenced tweet actually exists? + +function parseTweetId(tweetRef) { + const match = tweetRef.match(TWEET_REGEX); + if (!match) { + throw new Error(`Invalid tweet reference: ${tweetRef}`); + } + return match[1]; +} diff --git a/lib/common/tweet.js b/lib/common/tweet.js index 37103fd2..7e6ea284 100644 --- a/lib/common/tweet.js +++ b/lib/common/tweet.js @@ -3,7 +3,7 @@ module.exports = tweet; const { TwitterApi } = require("twitter-api-v2"); const mime = require("mime-types"); -const TWEET_REGEX = /^https:\/\/twitter\.com\/[^/]+\/status\/(\d+)$/; +const parseTweetId = require("./parse-tweet-id"); async function tweet({ twitterCredentials }, tweetData, tweetFile) { const client = new TwitterApi(twitterCredentials); @@ -16,9 +16,8 @@ async function tweet({ twitterCredentials }, tweetData, tweetFile) { async function handleTweet(client, self, tweet, name) { if (tweet.retweet && !tweet.text) { - // TODO: Should this throw if an invalid tweet is passed and there is no match? - const match = tweet.retweet.match(TWEET_REGEX); - if (match) return createRetweet(client, self, match[1]); + const tweetId = parseTweetId(tweet.retweet); + if (tweetId) return createRetweet(client, self, tweetId); } const tweetData = { @@ -33,19 +32,17 @@ async function handleTweet(client, self, tweet, name) { } if (tweet.reply) { - // TODO: Should this throw if an invalid reply is passed and there is no match? - const match = tweet.reply.match(TWEET_REGEX); - if (match) { + const tweetId = parseTweetId(tweet.reply); + if (tweetId) { tweetData.reply = { - in_reply_to_tweet_id: match[1], + in_reply_to_tweet_id: tweetId, }; } } if (tweet.retweet) { - // TODO: Should this throw if an invalid tweet is passed and there is no match? - const match = tweet.retweet.match(TWEET_REGEX); - if (match) tweetData.quote_tweet_id = match[1]; + const tweetId = parseTweetId(tweet.retweet); + if (tweetId) tweetData.quote_tweet_id = tweetId; } if (tweet.media?.length) { diff --git a/lib/index.js b/lib/index.js index 0350c77b..550c7bae 100644 --- a/lib/index.js +++ b/lib/index.js @@ -46,6 +46,10 @@ async function main() { const ref = process.env.GITHUB_REF; const sha = process.env.GITHUB_SHA; const dir = process.env.GITHUB_WORKSPACE; + const trigger = process.env.GITHUB_EVENT_NAME; + // optionally allow enabling comments them on "pull_request" for local branches + const enableComments = !!process.env.ENABLE_COMMENTS; + const githubState = { ...state, toolkit, @@ -54,15 +58,15 @@ async function main() { ref, sha, dir, + trigger, + enableComments, }; - switch (process.env.GITHUB_EVENT_NAME) { - case "push": - await handlePush(githubState); - break; - case "pull_request": - await handlePullRequest(githubState); - break; + if (trigger === "push") { + await handlePush(githubState); + } + if (trigger === "pull_request" || trigger === "pull_request_target") { + await handlePullRequest(githubState); } } diff --git a/lib/pull-request/create-check-run.js b/lib/pull-request/create-check-run.js index 966c4182..53086927 100644 --- a/lib/pull-request/create-check-run.js +++ b/lib/pull-request/create-check-run.js @@ -1,49 +1,12 @@ module.exports = createCheckRun; -const { autoLink } = require("twitter-text"); - -const parseTweetFileContent = require("../common/parse-tweet-file-content"); - async function createCheckRun( - { octokit, payload, startedAt, toolkit, dir }, - newTweets + { payload, startedAt, octokit, toolkit }, + summary ) { - const parsedTweets = newTweets.map((rawTweet) => { - try { - return parseTweetFileContent(rawTweet, dir); - } catch (error) { - return { - error: error.message, - valid: false, - text: rawTweet, - }; - } - }); - - const allTweetsValid = parsedTweets.every((tweet) => tweet.valid); - - // Check runs cannot be created if the pull request was created by a fork, - // so we just log out the result. - // https://help.github.com/en/actions/automating-your-workflow-with-github-actions/authenticating-with-the-github_token#permissions-for-the-github_token - if (payload.pull_request.head.repo.fork) { - for (const tweet of parsedTweets) { - if (tweet.valid) { - toolkit.info(`### ✅ Valid\n\n${tweet.text}`); - } else { - toolkit.info( - `### ❌ Invalid\n\n${tweet.text}\n\n${tweet.error || "Unknown error"}` - ); - } - } - process.exit(allTweetsValid ? 0 : 1); - } - const response = await octokit.request( "POST /repos/:owner/:repo/check-runs", { - headers: { - accept: "application/vnd.github.antiope-preview+json", - }, owner: payload.repository.owner.login, repo: payload.repository.name, name: "preview", @@ -51,27 +14,12 @@ async function createCheckRun( started_at: startedAt, completed_at: new Date().toISOString(), status: "completed", - conclusion: allTweetsValid ? "success" : "failure", + conclusion: summary.valid ? "success" : "failure", output: { - title: `${parsedTweets.length} tweet(s)`, - summary: parsedTweets.map(tweetToCheckRunSummary).join("\n\n---\n\n"), + title: `${summary.count} tweet(s)`, + summary: summary.body, }, } ); - toolkit.info(`check run created: ${response.data.html_url}`); } - -function tweetToCheckRunSummary(tweet) { - let text = autoLink(tweet.text) - .replace(/(^|\n)/g, "$1> ") - .replace(/(^|\n)> (\n|$)/g, "$1>$2"); - - if (!tweet.valid) - return `### ❌ Invalid\n\n${text}\n\n${tweet.error || "Unknown error"}`; - - if (tweet.poll) - text += - "\n\nThe tweet includes a poll:\n\n> 🔘 " + tweet.poll.join("\n> 🔘 "); - return `### ✅ Valid\n\n${text}`; -} diff --git a/lib/pull-request/create-comment.js b/lib/pull-request/create-comment.js new file mode 100644 index 00000000..2949b9c6 --- /dev/null +++ b/lib/pull-request/create-comment.js @@ -0,0 +1,50 @@ +module.exports = createComment; + +const BOT_LOGIN = "github-actions[bot]"; + +const DIVIDER = "\n\n---\n\n*"; +const PREVIEW = `${DIVIDER}Preview using `; +const UPDATED = `${DIVIDER}**Updated** preview using `; +const SIGNATURE = + " generated by [Twitter, together!](https://github.com/twitter-together/action)*"; + +async function createComment({ octokit, payload }, summary) { + const comment = `${summary.title}${summary.body}`; + // check for existing comments. + const comments = await octokit.request( + "GET /repos/{owner}/{repo}/issues/{issue_number}/comments", + { + owner: payload.repository.owner.login, + repo: payload.repository.name, + issue_number: payload.pull_request.number, + } + ); + + const match = comments.data.find( + ({ user, body }) => user.login === BOT_LOGIN && body.endsWith(SIGNATURE) + ); + + if (match) { + // update the existing comment + await octokit.request( + "PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}", + { + owner: payload.repository.owner.login, + repo: payload.repository.name, + comment_id: match.id, + body: `${comment}${UPDATED}${payload.pull_request.head.sha}${SIGNATURE}`, + } + ); + } else { + // post a new comment + await octokit.request( + "POST /repos/{owner}/{repo}/issues/{issue_number}/comments", + { + owner: payload.repository.owner.login, + repo: payload.repository.name, + issue_number: payload.pull_request.number, + body: `${comment}${PREVIEW}${payload.pull_request.head.sha}${SIGNATURE}`, + } + ); + } +} diff --git a/lib/pull-request/generate-summary.js b/lib/pull-request/generate-summary.js new file mode 100644 index 00000000..f50bcc8e --- /dev/null +++ b/lib/pull-request/generate-summary.js @@ -0,0 +1,81 @@ +module.exports = generateSummary; + +const { autoLink } = require("twitter-text"); + +const parseTweetFileContent = require("../common/parse-tweet-file-content"); +const getNewTweets = require("./get-new-tweets"); + +async function generateSummary(state, plainText = false) { + const { payload, dir } = state; + + const newTweets = await getNewTweets(state); + + const parsedTweets = newTweets.map((tweet) => { + try { + return parseTweetFileContent(tweet, dir); + } catch (error) { + return { + error: error.message, + valid: false, + text: tweet, + }; + } + }); + + return { + count: parsedTweets.length, + valid: parsedTweets.every((tweet) => tweet.valid), + title: `## Found ${parsedTweets.length} new \`.tweet\` file(s)\n\n`, + body: parsedTweets + .map((tweet) => summarizeTweet({ tweet, payload, dir, plainText })) + .join("\n\n---\n\n"), + }; +} + +function summarizeTweet(state, threading = false) { + const { tweet, payload, dir, plainText } = state; + + if (!tweet.valid) + return `### ❌ Invalid Tweet\n\n\`\`\`tweet\n${tweet.text}\n\`\`\`\n\n**${ + tweet.error || "Unknown error" + }**`; + + let text = !tweet.text + ? "" + : autoLink(tweet.text) + .replace(/(^|\n)/g, "$1> ") + .replace(/(^|\n)> (\n|$)/g, "$1>$2"); + + if (tweet.poll) text = `- ${tweet.poll.join("\n- ")}\n\n${text}`; + + if (tweet.reply) text = `Replying to ${tweet.reply}\n\n${text}`; + + if (tweet.retweet) text = `Retweeting ${tweet.retweet}\n\n${text}`.trim(); + + if (tweet.media.length) { + const media = tweet.media + .map(({ file, alt }) => { + const fileName = file.replace(dir, ""); + if (plainText) { + return `- ${fileName}${alt && ` [${alt}]`}`; + } else { + const { repo, sha } = payload.pull_request.head; + return `${alt || ""}\n`; + } + }) + .join(plainText ? "\n" : "\n\n"); + text = `${media}\n\n${text}`.trim(); + } + + if (tweet.thread || threading) { + const count = threading ? threading + 1 : 1; + let thread = `\n\n#### --- 🧵 ${count} ---\n\n${text}`; + if (tweet.thread) + thread += summarizeTweet({ ...state, tweet: tweet.thread }, count); + return threading ? thread : `### ✅ Valid Thread${thread}`; + } + + return `### ✅ Valid Tweet\n\n${text}`; +} diff --git a/lib/pull-request/index.js b/lib/pull-request/index.js index 14eb5f14..f10070f1 100644 --- a/lib/pull-request/index.js +++ b/lib/pull-request/index.js @@ -1,14 +1,21 @@ module.exports = handlePullRequest; -const getNewTweets = require("./get-new-tweets"); const createCheckRun = require("./create-check-run"); +const createComment = require("./create-comment"); +const generateSummary = require("./generate-summary"); + +const CHECK = "check"; +const LOG = "log"; +const COMMENT = "comment"; async function handlePullRequest(state) { - const { octokit, toolkit, payload } = state; + const { octokit, toolkit, payload, trigger, enableComments } = state; - // ignore builds from branches other than the repository’s defaul branch + // ignore builds from branches other than the repository’s default branch const base = payload.pull_request.base.ref; const defaultBranch = payload.repository.default_branch; + const fork = !!payload.pull_request.head.repo.fork; + if (defaultBranch !== base) { return toolkit.info( `Pull request base "${base}" is not the repository’s default branch` @@ -22,6 +29,29 @@ async function handlePullRequest(state) { process.exit(); }); - const newTweets = await getNewTweets(state); - await createCheckRun(state, newTweets); + // default report type, for `pull_request` non-fork PRs + let reportType = CHECK; + // can only log the output for fork PRs without check run permissions + if (fork && trigger === "pull_request") { + reportType = LOG; + } + // assume use of comments for `pull_request_target` PRs + // optional: use comments instead of check runs for `pull_request` + if (trigger === "pull_request_target" || enableComments) { + reportType = COMMENT; + } + + const summary = await generateSummary(state, reportType === LOG); + + if (reportType === LOG) { + toolkit.info(summary.body); + process.exit(summary.valid ? 0 : 1); + } + if (reportType === COMMENT) { + await createComment(state, summary); + process.exit(summary.valid ? 0 : 1); + } + if (reportType === CHECK) { + await createCheckRun(state, summary); + } } diff --git a/test/pull-request-has-tweet-issue-comment-updated/event.json b/test/pull-request-has-tweet-issue-comment-updated/event.json new file mode 100644 index 00000000..ad692d8e --- /dev/null +++ b/test/pull-request-has-tweet-issue-comment-updated/event.json @@ -0,0 +1,22 @@ +{ + "action": "opened", + "pull_request": { + "number": 123, + "base": { + "ref": "main" + }, + "head": { + "sha": "0000000000000000000000000000000000000002", + "repo": { + "fork": false + } + } + }, + "repository": { + "default_branch": "main", + "name": "action", + "owner": { + "login": "twitter-together" + } + } +} diff --git a/test/pull-request-has-tweet-issue-comment-updated/test.js b/test/pull-request-has-tweet-issue-comment-updated/test.js new file mode 100644 index 00000000..4767872e --- /dev/null +++ b/test/pull-request-has-tweet-issue-comment-updated/test.js @@ -0,0 +1,116 @@ +/** + * This test checks the happy path of pull request adding a new *.tweet file + */ + +const nock = require("nock"); +const tap = require("tap"); + +// SETUP +process.env.GITHUB_EVENT_NAME = "pull_request"; +process.env.GITHUB_TOKEN = "secret123"; +process.env.GITHUB_EVENT_PATH = require.resolve("./event.json"); + +// set other env variables so action-toolkit is happy +process.env.GITHUB_REF = ""; +process.env.GITHUB_WORKSPACE = ""; +process.env.GITHUB_WORKFLOW = ""; +process.env.GITHUB_ACTION = "twitter-together"; +process.env.GITHUB_ACTOR = ""; +process.env.GITHUB_REPOSITORY = ""; +process.env.GITHUB_SHA = ""; +process.env.ENABLE_COMMENTS = "1"; + +// MOCK +nock("https://api.github.com", { + reqheaders: { + authorization: "token secret123", + }, +}) + // get changed files + .get("/repos/twitter-together/action/pulls/123/files") + .reply(200, [ + { + status: "added", + filename: "tweets/hello-world.tweet", + }, + ]); + +// get pull request diff +nock("https://api.github.com", { + reqheaders: { + accept: "application/vnd.github.diff", + authorization: "token secret123", + }, +}) + .get("/repos/twitter-together/action/pulls/123") + .reply( + 200, + `diff --git a/tweets/hello-world.tweet b/tweets/hello-world.tweet +new file mode 100644 +index 0000000..0123456 +--- /dev/null ++++ b/tweets/hello-world.tweet +@@ -0,0 +1 @@ ++Hello, world!` + ); + +// check for comments +nock("https://api.github.com", { + reqheaders: { + authorization: "token secret123", + }, +}) + // has a comment + .get("/repos/twitter-together/action/issues/123/comments") + .reply(200, [ + { + id: 454, + user: { login: "some-other-account" }, + body: "blah", + }, + { + id: 455, + user: { login: "github-actions[bot]" }, + body: "A comment generated by some other action", + }, + { + id: 456, + user: { login: "github-actions[bot]" }, + body: `## Found 1 new \`.tweet\` file(s) + +### ✅ Valid Tweet + +> Hello, world! + +--- + +*Preview using 0000000000000000000000000000000000000002 generated by [Twitter, together!](https://github.com/twitter-together/action)*`, + }, + ]); + +// update comment +nock("https://api.github.com") + // get changed files + .patch("/repos/twitter-together/action/issues/comments/456", ({ body }) => { + tap.same( + body, + `## Found 1 new \`.tweet\` file(s) + +### ✅ Valid Tweet + +> Hello, world! + +--- + +***Updated** preview using 0000000000000000000000000000000000000002 generated by [Twitter, together!](https://github.com/twitter-together/action)*` + ); + return true; + }) + .reply(201); + +process.on("exit", (code) => { + tap.equal(code, 0); + tap.same(nock.pendingMocks(), []); +}); + +require("../../lib"); diff --git a/test/pull-request-has-tweet-issue-comment/event.json b/test/pull-request-has-tweet-issue-comment/event.json new file mode 100644 index 00000000..ad692d8e --- /dev/null +++ b/test/pull-request-has-tweet-issue-comment/event.json @@ -0,0 +1,22 @@ +{ + "action": "opened", + "pull_request": { + "number": 123, + "base": { + "ref": "main" + }, + "head": { + "sha": "0000000000000000000000000000000000000002", + "repo": { + "fork": false + } + } + }, + "repository": { + "default_branch": "main", + "name": "action", + "owner": { + "login": "twitter-together" + } + } +} diff --git a/test/pull-request-has-tweet-issue-comment/test.js b/test/pull-request-has-tweet-issue-comment/test.js new file mode 100644 index 00000000..f4592abe --- /dev/null +++ b/test/pull-request-has-tweet-issue-comment/test.js @@ -0,0 +1,92 @@ +/** + * This test checks the happy path of pull request adding a new *.tweet file + */ + +const nock = require("nock"); +const tap = require("tap"); + +// SETUP +process.env.GITHUB_EVENT_NAME = "pull_request"; +process.env.GITHUB_TOKEN = "secret123"; +process.env.GITHUB_EVENT_PATH = require.resolve("./event.json"); + +// set other env variables so action-toolkit is happy +process.env.GITHUB_REF = ""; +process.env.GITHUB_WORKSPACE = ""; +process.env.GITHUB_WORKFLOW = ""; +process.env.GITHUB_ACTION = "twitter-together"; +process.env.GITHUB_ACTOR = ""; +process.env.GITHUB_REPOSITORY = ""; +process.env.GITHUB_SHA = ""; +process.env.ENABLE_COMMENTS = "1"; + +// MOCK +nock("https://api.github.com", { + reqheaders: { + authorization: "token secret123", + }, +}) + // get changed files + .get("/repos/twitter-together/action/pulls/123/files") + .reply(200, [ + { + status: "added", + filename: "tweets/hello-world.tweet", + }, + ]); + +// get pull request diff +nock("https://api.github.com", { + reqheaders: { + accept: "application/vnd.github.diff", + authorization: "token secret123", + }, +}) + .get("/repos/twitter-together/action/pulls/123") + .reply( + 200, + `diff --git a/tweets/hello-world.tweet b/tweets/hello-world.tweet +new file mode 100644 +index 0000000..0123456 +--- /dev/null ++++ b/tweets/hello-world.tweet +@@ -0,0 +1 @@ ++Hello, world!` + ); + +// check for comments +nock("https://api.github.com", { + reqheaders: { + authorization: "token secret123", + }, +}) + // no comments + .get("/repos/twitter-together/action/issues/123/comments") + .reply(200, []); + +// post comment +nock("https://api.github.com") + // get changed files + .post("/repos/twitter-together/action/issues/123/comments", ({ body }) => { + tap.same( + body, + `## Found 1 new \`.tweet\` file(s) + +### ✅ Valid Tweet + +> Hello, world! + +--- + +*Preview using 0000000000000000000000000000000000000002 generated by [Twitter, together!](https://github.com/twitter-together/action)*` + ); + return true; + }) + .reply(201); + +process.on("exit", (code) => { + tap.equal(code, 0); + tap.same(nock.pendingMocks(), []); +}); + +require("../../lib"); diff --git a/test/pull-request-has-tweet-no-newline/test.js b/test/pull-request-has-tweet-no-newline/test.js index 864069ce..8c5c37da 100644 --- a/test/pull-request-has-tweet-no-newline/test.js +++ b/test/pull-request-has-tweet-no-newline/test.js @@ -65,7 +65,7 @@ nock("https://api.github.com") tap.equal(body.conclusion, "success"); tap.same(body.output, { title: "1 tweet(s)", - summary: "### ✅ Valid\n\n> Hello, world!", + summary: "### ✅ Valid Tweet\n\n> Hello, world!", }); return true; diff --git a/test/pull-request-has-tweet-with-front-matter-media/event.json b/test/pull-request-has-tweet-with-front-matter-media/event.json new file mode 100644 index 00000000..6eb7c183 --- /dev/null +++ b/test/pull-request-has-tweet-with-front-matter-media/event.json @@ -0,0 +1,27 @@ +{ + "action": "opened", + "after": "0000000000000000000000000000000000000003", + "pull_request": { + "number": 123, + "base": { + "ref": "main" + }, + "head": { + "sha": "0000000000000000000000000000000000000003", + "repo": { + "name": "action", + "fork": false, + "owner": { + "login": "twitter-together" + } + } + } + }, + "repository": { + "default_branch": "main", + "name": "action", + "owner": { + "login": "twitter-together" + } + } +} diff --git a/test/pull-request-has-tweet-with-front-matter-media/media/cat.jpg b/test/pull-request-has-tweet-with-front-matter-media/media/cat.jpg new file mode 100644 index 00000000..a10d5059 Binary files /dev/null and b/test/pull-request-has-tweet-with-front-matter-media/media/cat.jpg differ diff --git a/test/pull-request-has-tweet-with-front-matter-media/media/nested/media/pets/dog.jpg b/test/pull-request-has-tweet-with-front-matter-media/media/nested/media/pets/dog.jpg new file mode 100644 index 00000000..7a25bfbe Binary files /dev/null and b/test/pull-request-has-tweet-with-front-matter-media/media/nested/media/pets/dog.jpg differ diff --git a/test/pull-request-has-tweet-with-front-matter-media/test.js b/test/pull-request-has-tweet-with-front-matter-media/test.js new file mode 100644 index 00000000..d77cbffb --- /dev/null +++ b/test/pull-request-has-tweet-with-front-matter-media/test.js @@ -0,0 +1,101 @@ +/** + * This test checks the happy path of pull request adding a new *.tweet file + */ + +const nock = require("nock"); +const tap = require("tap"); +const path = require("path"); + +// SETUP +process.env.GITHUB_EVENT_NAME = "pull_request"; +process.env.GITHUB_TOKEN = "secret123"; +process.env.GITHUB_EVENT_PATH = require.resolve("./event.json"); + +// set other env variables so action-toolkit is happy +process.env.GITHUB_REF = ""; +process.env.GITHUB_WORKSPACE = path.dirname(process.env.GITHUB_EVENT_PATH); +process.env.GITHUB_WORKFLOW = ""; +process.env.GITHUB_ACTION = "twitter-together"; +process.env.GITHUB_ACTOR = ""; +process.env.GITHUB_REPOSITORY = ""; +process.env.GITHUB_SHA = ""; + +// MOCK +nock("https://api.github.com", { + reqheaders: { + authorization: "token secret123", + }, +}) + // get changed files + .get("/repos/twitter-together/action/pulls/123/files") + .reply(200, [ + { + status: "added", + filename: "tweets/hello-world.tweet", + }, + ]); + +// get pull request diff +nock("https://api.github.com", { + reqheaders: { + accept: "application/vnd.github.diff", + authorization: "token secret123", + }, +}) + .get("/repos/twitter-together/action/pulls/123") + .reply( + 200, + `diff --git a/media/cat.jpg b/media/cat.jpg +new file mode 100644 +index 0000000..a10d505 +Binary files /dev/null and b/media/cat.jpg differ +diff --git a/media/dog.jpg b/media/dog.jpg +new file mode 100644 +index 0000000..7a25bfb +Binary files /dev/null and b/media/dog.jpg differ +diff --git a/tweets/media.tweet b/tweets/media.tweet +new file mode 100644 +index 0000000..1715c04 +--- /dev/null ++++ b/tweets/media.tweet +@@ -0,0 +1,9 @@ ++--- ++media: ++ - file: cat.jpg ++ - file: nested/media/pets/dog.jpg ++ alt: A dog ++--- ++ ++Here are some cute animals!` + ); + +// create check run +nock("https://api.github.com") + // get changed files + .post("/repos/twitter-together/action/check-runs", (body) => { + tap.equal(body.name, "preview"); + tap.equal(body.head_sha, "0000000000000000000000000000000000000003"); + tap.equal(body.status, "completed"); + tap.equal(body.conclusion, "success"); + tap.same(body.output, { + title: "1 tweet(s)", + summary: `### ✅ Valid Tweet + + + +A dog + + +> Here are some cute animals!`, + }); + + return true; + }) + .reply(201); + +process.on("exit", (code) => { + tap.equal(code, 0); + tap.same(nock.pendingMocks(), []); +}); + +require("../../lib"); diff --git a/test/pull-request-has-tweet-with-front-matter-poll-5-options/event.json b/test/pull-request-has-tweet-with-front-matter-poll-5-options/event.json new file mode 100644 index 00000000..ad692d8e --- /dev/null +++ b/test/pull-request-has-tweet-with-front-matter-poll-5-options/event.json @@ -0,0 +1,22 @@ +{ + "action": "opened", + "pull_request": { + "number": 123, + "base": { + "ref": "main" + }, + "head": { + "sha": "0000000000000000000000000000000000000002", + "repo": { + "fork": false + } + } + }, + "repository": { + "default_branch": "main", + "name": "action", + "owner": { + "login": "twitter-together" + } + } +} diff --git a/test/pull-request-has-tweet-with-front-matter-poll-5-options/test.js b/test/pull-request-has-tweet-with-front-matter-poll-5-options/test.js new file mode 100644 index 00000000..d65c9a4b --- /dev/null +++ b/test/pull-request-has-tweet-with-front-matter-poll-5-options/test.js @@ -0,0 +1,102 @@ +/** + * This test checks the happy path of pull request adding a new *.tweet file + */ + +const nock = require("nock"); +const tap = require("tap"); + +// SETUP +process.env.GITHUB_EVENT_NAME = "pull_request"; +process.env.GITHUB_TOKEN = "secret123"; +process.env.GITHUB_EVENT_PATH = require.resolve("./event.json"); + +// set other env variables so action-toolkit is happy +process.env.GITHUB_REF = ""; +process.env.GITHUB_WORKSPACE = ""; +process.env.GITHUB_WORKFLOW = ""; +process.env.GITHUB_ACTION = "twitter-together"; +process.env.GITHUB_ACTOR = ""; +process.env.GITHUB_REPOSITORY = ""; +process.env.GITHUB_SHA = ""; + +// MOCK +nock("https://api.github.com", { + reqheaders: { + authorization: "token secret123", + }, +}) + // get changed files + .get("/repos/twitter-together/action/pulls/123/files") + .reply(200, [ + { + status: "added", + filename: "tweets/hello-world.tweet", + }, + ]); + +// get pull request diff +nock("https://api.github.com", { + reqheaders: { + accept: "application/vnd.github.diff", + authorization: "token secret123", + }, +}) + .get("/repos/twitter-together/action/pulls/123") + .reply( + 200, + `diff --git a/tweets/poll.tweet b/tweets/poll.tweet +new file mode 100644 +index 0000000..6053708 +--- /dev/null ++++ b/tweets/poll.tweet +@@ -0,0 +1,10 @@ ++--- ++poll: ++ - Red ++ - Blue ++ - Green ++ - Orange ++ - Purple ++--- ++ ++What is your favorite color?` + ); + +// create check run +nock("https://api.github.com") + // get changed files + .post("/repos/twitter-together/action/check-runs", (body) => { + tap.equal(body.name, "preview"); + tap.equal(body.head_sha, "0000000000000000000000000000000000000002"); + tap.equal(body.status, "completed"); + tap.equal(body.conclusion, "failure"); + tap.same(body.output, { + title: "1 tweet(s)", + summary: `### ❌ Invalid Tweet + +\`\`\`tweet +--- +poll: + - Red + - Blue + - Green + - Orange + - Purple +--- + +What is your favorite color? +\`\`\` + +**Polls cannot have more than four options, found 5 options**`, + }); + + return true; + }) + .reply(201); + +process.on("exit", (code) => { + tap.equal(code, 0); + tap.same(nock.pendingMocks(), []); +}); + +require("../../lib"); diff --git a/test/pull-request-has-tweet-with-front-matter-poll/event.json b/test/pull-request-has-tweet-with-front-matter-poll/event.json new file mode 100644 index 00000000..ad692d8e --- /dev/null +++ b/test/pull-request-has-tweet-with-front-matter-poll/event.json @@ -0,0 +1,22 @@ +{ + "action": "opened", + "pull_request": { + "number": 123, + "base": { + "ref": "main" + }, + "head": { + "sha": "0000000000000000000000000000000000000002", + "repo": { + "fork": false + } + } + }, + "repository": { + "default_branch": "main", + "name": "action", + "owner": { + "login": "twitter-together" + } + } +} diff --git a/test/pull-request-has-tweet-with-front-matter-poll/test.js b/test/pull-request-has-tweet-with-front-matter-poll/test.js new file mode 100644 index 00000000..8ab77e31 --- /dev/null +++ b/test/pull-request-has-tweet-with-front-matter-poll/test.js @@ -0,0 +1,91 @@ +/** + * This test checks the happy path of pull request adding a new *.tweet file + */ + +const nock = require("nock"); +const tap = require("tap"); + +// SETUP +process.env.GITHUB_EVENT_NAME = "pull_request"; +process.env.GITHUB_TOKEN = "secret123"; +process.env.GITHUB_EVENT_PATH = require.resolve("./event.json"); + +// set other env variables so action-toolkit is happy +process.env.GITHUB_REF = ""; +process.env.GITHUB_WORKSPACE = ""; +process.env.GITHUB_WORKFLOW = ""; +process.env.GITHUB_ACTION = "twitter-together"; +process.env.GITHUB_ACTOR = ""; +process.env.GITHUB_REPOSITORY = ""; +process.env.GITHUB_SHA = ""; + +// MOCK +nock("https://api.github.com", { + reqheaders: { + authorization: "token secret123", + }, +}) + // get changed files + .get("/repos/twitter-together/action/pulls/123/files") + .reply(200, [ + { + status: "added", + filename: "tweets/hello-world.tweet", + }, + ]); + +// get pull request diff +nock("https://api.github.com", { + reqheaders: { + accept: "application/vnd.github.diff", + authorization: "token secret123", + }, +}) + .get("/repos/twitter-together/action/pulls/123") + .reply( + 200, + `diff --git a/tweets/poll.tweet b/tweets/poll.tweet +new file mode 100644 +index 0000000..6053708 +--- /dev/null ++++ b/tweets/poll.tweet +@@ -0,0 +1,8 @@ ++--- ++poll: ++ - Red ++ - Blue ++ - Green ++--- ++ ++What is your favorite color?` + ); + +// create check run +nock("https://api.github.com") + // get changed files + .post("/repos/twitter-together/action/check-runs", (body) => { + tap.equal(body.name, "preview"); + tap.equal(body.head_sha, "0000000000000000000000000000000000000002"); + tap.equal(body.status, "completed"); + tap.equal(body.conclusion, "success"); + tap.same(body.output, { + title: "1 tweet(s)", + summary: `### ✅ Valid Tweet + +- Red +- Blue +- Green + +> What is your favorite color?`, + }); + + return true; + }) + .reply(201); + +process.on("exit", (code) => { + tap.equal(code, 0); + tap.same(nock.pendingMocks(), []); +}); + +require("../../lib"); diff --git a/test/pull-request-has-tweet-with-front-matter-quote/event.json b/test/pull-request-has-tweet-with-front-matter-quote/event.json new file mode 100644 index 00000000..ad692d8e --- /dev/null +++ b/test/pull-request-has-tweet-with-front-matter-quote/event.json @@ -0,0 +1,22 @@ +{ + "action": "opened", + "pull_request": { + "number": 123, + "base": { + "ref": "main" + }, + "head": { + "sha": "0000000000000000000000000000000000000002", + "repo": { + "fork": false + } + } + }, + "repository": { + "default_branch": "main", + "name": "action", + "owner": { + "login": "twitter-together" + } + } +} diff --git a/test/pull-request-has-tweet-with-front-matter-quote/test.js b/test/pull-request-has-tweet-with-front-matter-quote/test.js new file mode 100644 index 00000000..e6594ac3 --- /dev/null +++ b/test/pull-request-has-tweet-with-front-matter-quote/test.js @@ -0,0 +1,86 @@ +/** + * This test checks the happy path of pull request adding a new *.tweet file + */ + +const nock = require("nock"); +const tap = require("tap"); + +// SETUP +process.env.GITHUB_EVENT_NAME = "pull_request"; +process.env.GITHUB_TOKEN = "secret123"; +process.env.GITHUB_EVENT_PATH = require.resolve("./event.json"); + +// set other env variables so action-toolkit is happy +process.env.GITHUB_REF = ""; +process.env.GITHUB_WORKSPACE = ""; +process.env.GITHUB_WORKFLOW = ""; +process.env.GITHUB_ACTION = "twitter-together"; +process.env.GITHUB_ACTOR = ""; +process.env.GITHUB_REPOSITORY = ""; +process.env.GITHUB_SHA = ""; + +// MOCK +nock("https://api.github.com", { + reqheaders: { + authorization: "token secret123", + }, +}) + // get changed files + .get("/repos/twitter-together/action/pulls/123/files") + .reply(200, [ + { + status: "added", + filename: "tweets/hello-world.tweet", + }, + ]); + +// get pull request diff +nock("https://api.github.com", { + reqheaders: { + accept: "application/vnd.github.diff", + authorization: "token secret123", + }, +}) + .get("/repos/twitter-together/action/pulls/123") + .reply( + 200, + `diff --git a/tweets/retweet.tweet b/tweets/retweet.tweet +new file mode 100644 +index 0000000..d462a1f +--- /dev/null ++++ b/tweets/retweet.tweet +@@ -0,0 +1,5 @@ ++--- ++retweet: https://twitter.com/m2rg/status/0000000000000000001 ++--- ++ ++Smart thinking!` + ); + +// create check run +nock("https://api.github.com") + // get changed files + .post("/repos/twitter-together/action/check-runs", (body) => { + tap.equal(body.name, "preview"); + tap.equal(body.head_sha, "0000000000000000000000000000000000000002"); + tap.equal(body.status, "completed"); + tap.equal(body.conclusion, "success"); + tap.same(body.output, { + title: "1 tweet(s)", + summary: `### ✅ Valid Tweet + +Retweeting https://twitter.com/m2rg/status/0000000000000000001 + +> Smart thinking!`, + }); + + return true; + }) + .reply(201); + +process.on("exit", (code) => { + tap.equal(code, 0); + tap.same(nock.pendingMocks(), []); +}); + +require("../../lib"); diff --git a/test/pull-request-has-tweet-with-front-matter-reply-invalid/event.json b/test/pull-request-has-tweet-with-front-matter-reply-invalid/event.json new file mode 100644 index 00000000..ad692d8e --- /dev/null +++ b/test/pull-request-has-tweet-with-front-matter-reply-invalid/event.json @@ -0,0 +1,22 @@ +{ + "action": "opened", + "pull_request": { + "number": 123, + "base": { + "ref": "main" + }, + "head": { + "sha": "0000000000000000000000000000000000000002", + "repo": { + "fork": false + } + } + }, + "repository": { + "default_branch": "main", + "name": "action", + "owner": { + "login": "twitter-together" + } + } +} diff --git a/test/pull-request-has-tweet-with-front-matter-reply-invalid/test.js b/test/pull-request-has-tweet-with-front-matter-reply-invalid/test.js new file mode 100644 index 00000000..7c61e21c --- /dev/null +++ b/test/pull-request-has-tweet-with-front-matter-reply-invalid/test.js @@ -0,0 +1,88 @@ +/** + * This test checks the happy path of pull request adding a new *.tweet file + */ + +const nock = require("nock"); +const tap = require("tap"); + +// SETUP +process.env.GITHUB_EVENT_NAME = "pull_request"; +process.env.GITHUB_TOKEN = "secret123"; +process.env.GITHUB_EVENT_PATH = require.resolve("./event.json"); + +// set other env variables so action-toolkit is happy +process.env.GITHUB_REF = ""; +process.env.GITHUB_WORKSPACE = ""; +process.env.GITHUB_WORKFLOW = ""; +process.env.GITHUB_ACTION = "twitter-together"; +process.env.GITHUB_ACTOR = ""; +process.env.GITHUB_REPOSITORY = ""; +process.env.GITHUB_SHA = ""; + +// MOCK +nock("https://api.github.com", { + reqheaders: { + authorization: "token secret123", + }, +}) + // get changed files + .get("/repos/twitter-together/action/pulls/123/files") + .reply(200, [ + { + status: "added", + filename: "tweets/hello-world.tweet", + }, + ]); + +// get pull request diff +nock("https://api.github.com", { + reqheaders: { + accept: "application/vnd.github.diff", + authorization: "token secret123", + }, +}) + .get("/repos/twitter-together/action/pulls/123") + .reply( + 200, + `diff --git a/tweets/reply.tweet b/tweets/reply.tweet +new file mode 100644 +index 0000000..d462a1f +--- /dev/null ++++ b/tweets/reply.tweet +@@ -0,0 +1,3 @@ ++--- ++reply: spoons ++---` + ); + +// create check run +nock("https://api.github.com") + // get changed files + .post("/repos/twitter-together/action/check-runs", (body) => { + tap.equal(body.name, "preview"); + tap.equal(body.head_sha, "0000000000000000000000000000000000000002"); + tap.equal(body.status, "completed"); + tap.equal(body.conclusion, "failure"); + tap.same(body.output, { + title: "1 tweet(s)", + summary: `### ❌ Invalid Tweet + +\`\`\`tweet +--- +reply: spoons +--- +\`\`\` + +**Invalid tweet reference: spoons**`, + }); + + return true; + }) + .reply(201); + +process.on("exit", (code) => { + tap.equal(code, 0); + tap.same(nock.pendingMocks(), []); +}); + +require("../../lib"); diff --git a/test/pull-request-has-tweet-with-front-matter-reply/event.json b/test/pull-request-has-tweet-with-front-matter-reply/event.json new file mode 100644 index 00000000..ad692d8e --- /dev/null +++ b/test/pull-request-has-tweet-with-front-matter-reply/event.json @@ -0,0 +1,22 @@ +{ + "action": "opened", + "pull_request": { + "number": 123, + "base": { + "ref": "main" + }, + "head": { + "sha": "0000000000000000000000000000000000000002", + "repo": { + "fork": false + } + } + }, + "repository": { + "default_branch": "main", + "name": "action", + "owner": { + "login": "twitter-together" + } + } +} diff --git a/test/pull-request-has-tweet-with-front-matter-reply/test.js b/test/pull-request-has-tweet-with-front-matter-reply/test.js new file mode 100644 index 00000000..f3c86c2e --- /dev/null +++ b/test/pull-request-has-tweet-with-front-matter-reply/test.js @@ -0,0 +1,86 @@ +/** + * This test checks the happy path of pull request adding a new *.tweet file + */ + +const nock = require("nock"); +const tap = require("tap"); + +// SETUP +process.env.GITHUB_EVENT_NAME = "pull_request"; +process.env.GITHUB_TOKEN = "secret123"; +process.env.GITHUB_EVENT_PATH = require.resolve("./event.json"); + +// set other env variables so action-toolkit is happy +process.env.GITHUB_REF = ""; +process.env.GITHUB_WORKSPACE = ""; +process.env.GITHUB_WORKFLOW = ""; +process.env.GITHUB_ACTION = "twitter-together"; +process.env.GITHUB_ACTOR = ""; +process.env.GITHUB_REPOSITORY = ""; +process.env.GITHUB_SHA = ""; + +// MOCK +nock("https://api.github.com", { + reqheaders: { + authorization: "token secret123", + }, +}) + // get changed files + .get("/repos/twitter-together/action/pulls/123/files") + .reply(200, [ + { + status: "added", + filename: "tweets/hello-world.tweet", + }, + ]); + +// get pull request diff +nock("https://api.github.com", { + reqheaders: { + accept: "application/vnd.github.diff", + authorization: "token secret123", + }, +}) + .get("/repos/twitter-together/action/pulls/123") + .reply( + 200, + `diff --git a/tweets/retweet.tweet b/tweets/retweet.tweet +new file mode 100644 +index 0000000..d462a1f +--- /dev/null ++++ b/tweets/retweet.tweet +@@ -0,0 +1,5 @@ ++--- ++reply: https://twitter.com/m2rg/status/0000000000000000001 ++--- ++ ++Good idea :)` + ); + +// create check run +nock("https://api.github.com") + // get changed files + .post("/repos/twitter-together/action/check-runs", (body) => { + tap.equal(body.name, "preview"); + tap.equal(body.head_sha, "0000000000000000000000000000000000000002"); + tap.equal(body.status, "completed"); + tap.equal(body.conclusion, "success"); + tap.same(body.output, { + title: "1 tweet(s)", + summary: `### ✅ Valid Tweet + +Replying to https://twitter.com/m2rg/status/0000000000000000001 + +> Good idea :)`, + }); + + return true; + }) + .reply(201); + +process.on("exit", (code) => { + tap.equal(code, 0); + tap.same(nock.pendingMocks(), []); +}); + +require("../../lib"); diff --git a/test/pull-request-has-tweet-with-front-matter-retweet-invalid/event.json b/test/pull-request-has-tweet-with-front-matter-retweet-invalid/event.json new file mode 100644 index 00000000..ad692d8e --- /dev/null +++ b/test/pull-request-has-tweet-with-front-matter-retweet-invalid/event.json @@ -0,0 +1,22 @@ +{ + "action": "opened", + "pull_request": { + "number": 123, + "base": { + "ref": "main" + }, + "head": { + "sha": "0000000000000000000000000000000000000002", + "repo": { + "fork": false + } + } + }, + "repository": { + "default_branch": "main", + "name": "action", + "owner": { + "login": "twitter-together" + } + } +} diff --git a/test/pull-request-has-tweet-with-front-matter-retweet-invalid/test.js b/test/pull-request-has-tweet-with-front-matter-retweet-invalid/test.js new file mode 100644 index 00000000..8e07169e --- /dev/null +++ b/test/pull-request-has-tweet-with-front-matter-retweet-invalid/test.js @@ -0,0 +1,88 @@ +/** + * This test checks the happy path of pull request adding a new *.tweet file + */ + +const nock = require("nock"); +const tap = require("tap"); + +// SETUP +process.env.GITHUB_EVENT_NAME = "pull_request"; +process.env.GITHUB_TOKEN = "secret123"; +process.env.GITHUB_EVENT_PATH = require.resolve("./event.json"); + +// set other env variables so action-toolkit is happy +process.env.GITHUB_REF = ""; +process.env.GITHUB_WORKSPACE = ""; +process.env.GITHUB_WORKFLOW = ""; +process.env.GITHUB_ACTION = "twitter-together"; +process.env.GITHUB_ACTOR = ""; +process.env.GITHUB_REPOSITORY = ""; +process.env.GITHUB_SHA = ""; + +// MOCK +nock("https://api.github.com", { + reqheaders: { + authorization: "token secret123", + }, +}) + // get changed files + .get("/repos/twitter-together/action/pulls/123/files") + .reply(200, [ + { + status: "added", + filename: "tweets/hello-world.tweet", + }, + ]); + +// get pull request diff +nock("https://api.github.com", { + reqheaders: { + accept: "application/vnd.github.diff", + authorization: "token secret123", + }, +}) + .get("/repos/twitter-together/action/pulls/123") + .reply( + 200, + `diff --git a/tweets/retweet.tweet b/tweets/retweet.tweet +new file mode 100644 +index 0000000..d462a1f +--- /dev/null ++++ b/tweets/retweet.tweet +@@ -0,0 +1,3 @@ ++--- ++retweet: spoons ++---` + ); + +// create check run +nock("https://api.github.com") + // get changed files + .post("/repos/twitter-together/action/check-runs", (body) => { + tap.equal(body.name, "preview"); + tap.equal(body.head_sha, "0000000000000000000000000000000000000002"); + tap.equal(body.status, "completed"); + tap.equal(body.conclusion, "failure"); + tap.same(body.output, { + title: "1 tweet(s)", + summary: `### ❌ Invalid Tweet + +\`\`\`tweet +--- +retweet: spoons +--- +\`\`\` + +**Invalid tweet reference: spoons**`, + }); + + return true; + }) + .reply(201); + +process.on("exit", (code) => { + tap.equal(code, 0); + tap.same(nock.pendingMocks(), []); +}); + +require("../../lib"); diff --git a/test/pull-request-has-tweet-with-front-matter-retweet/event.json b/test/pull-request-has-tweet-with-front-matter-retweet/event.json new file mode 100644 index 00000000..ad692d8e --- /dev/null +++ b/test/pull-request-has-tweet-with-front-matter-retweet/event.json @@ -0,0 +1,22 @@ +{ + "action": "opened", + "pull_request": { + "number": 123, + "base": { + "ref": "main" + }, + "head": { + "sha": "0000000000000000000000000000000000000002", + "repo": { + "fork": false + } + } + }, + "repository": { + "default_branch": "main", + "name": "action", + "owner": { + "login": "twitter-together" + } + } +} diff --git a/test/pull-request-has-tweet-with-front-matter-retweet/test.js b/test/pull-request-has-tweet-with-front-matter-retweet/test.js new file mode 100644 index 00000000..ea257f45 --- /dev/null +++ b/test/pull-request-has-tweet-with-front-matter-retweet/test.js @@ -0,0 +1,82 @@ +/** + * This test checks the happy path of pull request adding a new *.tweet file + */ + +const nock = require("nock"); +const tap = require("tap"); + +// SETUP +process.env.GITHUB_EVENT_NAME = "pull_request"; +process.env.GITHUB_TOKEN = "secret123"; +process.env.GITHUB_EVENT_PATH = require.resolve("./event.json"); + +// set other env variables so action-toolkit is happy +process.env.GITHUB_REF = ""; +process.env.GITHUB_WORKSPACE = ""; +process.env.GITHUB_WORKFLOW = ""; +process.env.GITHUB_ACTION = "twitter-together"; +process.env.GITHUB_ACTOR = ""; +process.env.GITHUB_REPOSITORY = ""; +process.env.GITHUB_SHA = ""; + +// MOCK +nock("https://api.github.com", { + reqheaders: { + authorization: "token secret123", + }, +}) + // get changed files + .get("/repos/twitter-together/action/pulls/123/files") + .reply(200, [ + { + status: "added", + filename: "tweets/hello-world.tweet", + }, + ]); + +// get pull request diff +nock("https://api.github.com", { + reqheaders: { + accept: "application/vnd.github.diff", + authorization: "token secret123", + }, +}) + .get("/repos/twitter-together/action/pulls/123") + .reply( + 200, + `diff --git a/tweets/retweet.tweet b/tweets/retweet.tweet +new file mode 100644 +index 0000000..d462a1f +--- /dev/null ++++ b/tweets/retweet.tweet +@@ -0,0 +1,3 @@ ++--- ++retweet: https://twitter.com/m2rg/status/0000000000000000001 ++---` + ); + +// create check run +nock("https://api.github.com") + // get changed files + .post("/repos/twitter-together/action/check-runs", (body) => { + tap.equal(body.name, "preview"); + tap.equal(body.head_sha, "0000000000000000000000000000000000000002"); + tap.equal(body.status, "completed"); + tap.equal(body.conclusion, "success"); + tap.same(body.output, { + title: "1 tweet(s)", + summary: `### ✅ Valid Tweet + +Retweeting https://twitter.com/m2rg/status/0000000000000000001`, + }); + + return true; + }) + .reply(201); + +process.on("exit", (code) => { + tap.equal(code, 0); + tap.same(nock.pendingMocks(), []); +}); + +require("../../lib"); diff --git a/test/pull-request-has-tweet-with-poll-with-5-options/test.js b/test/pull-request-has-tweet-with-poll-with-5-options/test.js index 1dd253dd..37d4024e 100644 --- a/test/pull-request-has-tweet-with-poll-with-5-options/test.js +++ b/test/pull-request-has-tweet-with-poll-with-5-options/test.js @@ -69,17 +69,19 @@ nock("https://api.github.com") tap.equal(body.conclusion, "failure"); tap.same(body.output, { title: "1 tweet(s)", - summary: `### ❌ Invalid + summary: `### ❌ Invalid Tweet -> Here is my poll -> -> ( ) option 1 -> ( ) option 2 -> ( ) option 3 -> ( ) option 4 -> ( ) option 5 +\`\`\`tweet +Here is my poll -Polls cannot have more than four options, found 5 options`, +( ) option 1 +( ) option 2 +( ) option 3 +( ) option 4 +( ) option 5 +\`\`\` + +**Polls cannot have more than four options, found 5 options**`, }); return true; diff --git a/test/pull-request-has-tweet-with-poll/test.js b/test/pull-request-has-tweet-with-poll/test.js index dcaf4c99..668acb15 100644 --- a/test/pull-request-has-tweet-with-poll/test.js +++ b/test/pull-request-has-tweet-with-poll/test.js @@ -68,16 +68,14 @@ nock("https://api.github.com") tap.equal(body.conclusion, "success"); tap.same(body.output, { title: "1 tweet(s)", - summary: `### ✅ Valid + summary: `### ✅ Valid Tweet -> Here is my poll +- option 1 +- option 2 +- option 3 +- option 4 -The tweet includes a poll: - -> 🔘 option 1 -> 🔘 option 2 -> 🔘 option 3 -> 🔘 option 4`, +> Here is my poll`, }); return true; diff --git a/test/pull-request-has-tweet-with-thread/event.json b/test/pull-request-has-tweet-with-thread/event.json new file mode 100644 index 00000000..ad692d8e --- /dev/null +++ b/test/pull-request-has-tweet-with-thread/event.json @@ -0,0 +1,22 @@ +{ + "action": "opened", + "pull_request": { + "number": 123, + "base": { + "ref": "main" + }, + "head": { + "sha": "0000000000000000000000000000000000000002", + "repo": { + "fork": false + } + } + }, + "repository": { + "default_branch": "main", + "name": "action", + "owner": { + "login": "twitter-together" + } + } +} diff --git a/test/pull-request-has-tweet-with-thread/test.js b/test/pull-request-has-tweet-with-thread/test.js new file mode 100644 index 00000000..955467f7 --- /dev/null +++ b/test/pull-request-has-tweet-with-thread/test.js @@ -0,0 +1,111 @@ +/** + * This test checks the happy path of pull request adding a new *.tweet file + */ + +const nock = require("nock"); +const tap = require("tap"); + +// SETUP +process.env.GITHUB_EVENT_NAME = "pull_request"; +process.env.GITHUB_TOKEN = "secret123"; +process.env.GITHUB_EVENT_PATH = require.resolve("./event.json"); + +// set other env variables so action-toolkit is happy +process.env.GITHUB_REF = ""; +process.env.GITHUB_WORKSPACE = ""; +process.env.GITHUB_WORKFLOW = ""; +process.env.GITHUB_ACTION = "twitter-together"; +process.env.GITHUB_ACTOR = ""; +process.env.GITHUB_REPOSITORY = ""; +process.env.GITHUB_SHA = ""; + +// MOCK +nock("https://api.github.com", { + reqheaders: { + authorization: "token secret123", + }, +}) + // get changed files + .get("/repos/twitter-together/action/pulls/123/files") + .reply(200, [ + { + status: "added", + filename: "tweets/hello-world.tweet", + }, + ]); + +// get pull request diff +nock("https://api.github.com", { + reqheaders: { + accept: "application/vnd.github.diff", + authorization: "token secret123", + }, +}) + .get("/repos/twitter-together/action/pulls/123") + .reply( + 200, + `diff --git a/tweets/thread.tweet b/tweets/thread.tweet +new file mode 100644 +index 0000000..ec04564 +--- /dev/null ++++ b/tweets/thread.tweet +@@ -0,0 +1,15 @@ ++🧵 Here is a thread... ++ ++--- ++--- ++poll: ++ - Banana ++ - Mango ++--- ++ ++Which fruit is more delicious? ++ ++--- ++We hope you enjoyed this thread... ++--- ++We certainly did.` + ); + +// create check run +nock("https://api.github.com") + // get changed files + .post("/repos/twitter-together/action/check-runs", (body) => { + tap.equal(body.name, "preview"); + tap.equal(body.head_sha, "0000000000000000000000000000000000000002"); + tap.equal(body.status, "completed"); + tap.equal(body.conclusion, "success"); + tap.same(body.output, { + title: "1 tweet(s)", + summary: `### ✅ Valid Thread + +#### --- 🧵 1 --- + +> 🧵 Here is a thread... + +#### --- 🧵 2 --- + +- Banana +- Mango + +> Which fruit is more delicious? + +#### --- 🧵 3 --- + +> We hope you enjoyed this thread... + +#### --- 🧵 4 --- + +> We certainly did.`, + }); + + return true; + }) + .reply(201); + +process.on("exit", (code) => { + tap.equal(code, 0); + tap.same(nock.pendingMocks(), []); +}); + +require("../../lib"); diff --git a/test/pull-request-has-tweet/test.js b/test/pull-request-has-tweet/test.js index 36d3c7a3..9be1e4c1 100644 --- a/test/pull-request-has-tweet/test.js +++ b/test/pull-request-has-tweet/test.js @@ -63,7 +63,7 @@ nock("https://api.github.com") tap.equal(body.conclusion, "success"); tap.same(body.output, { title: "1 tweet(s)", - summary: "### ✅ Valid\n\n> Hello, world!", + summary: "### ✅ Valid Tweet\n\n> Hello, world!", }); return true; diff --git a/test/pull-request-invalid-tweet/test.js b/test/pull-request-invalid-tweet/test.js index c984bd10..2341b056 100644 --- a/test/pull-request-invalid-tweet/test.js +++ b/test/pull-request-invalid-tweet/test.js @@ -66,11 +66,13 @@ nock("https://api.github.com", { tap.equal(body.conclusion, "failure"); tap.same(body.output, { title: "1 tweet(s)", - summary: `### ❌ Invalid + summary: `### ❌ Invalid Tweet -> Cupcake ipsum dolor sit amet chupa chups candy halvah I love. Apple pie gummi bears chupa chups jujubes I love cake jelly. Jelly candy canes pudding jujubes caramels sweet roll I love. Sweet fruitcake oat cake I love brownie sesame snaps apple pie lollipop. Pie dragée I love apple pie cotton candy candy chocolate bar. +\`\`\`tweet +Cupcake ipsum dolor sit amet chupa chups candy halvah I love. Apple pie gummi bears chupa chups jujubes I love cake jelly. Jelly candy canes pudding jujubes caramels sweet roll I love. Sweet fruitcake oat cake I love brownie sesame snaps apple pie lollipop. Pie dragée I love apple pie cotton candy candy chocolate bar. +\`\`\` -Tweet exceeds maximum length of 280 characters by 39 characters`, +**Tweet exceeds maximum length of 280 characters by 39 characters**`, }); return true; diff --git a/test/pull-request-target-has-tweet-issue-comment/event.json b/test/pull-request-target-has-tweet-issue-comment/event.json new file mode 100644 index 00000000..ad692d8e --- /dev/null +++ b/test/pull-request-target-has-tweet-issue-comment/event.json @@ -0,0 +1,22 @@ +{ + "action": "opened", + "pull_request": { + "number": 123, + "base": { + "ref": "main" + }, + "head": { + "sha": "0000000000000000000000000000000000000002", + "repo": { + "fork": false + } + } + }, + "repository": { + "default_branch": "main", + "name": "action", + "owner": { + "login": "twitter-together" + } + } +} diff --git a/test/pull-request-target-has-tweet-issue-comment/test.js b/test/pull-request-target-has-tweet-issue-comment/test.js new file mode 100644 index 00000000..6678b992 --- /dev/null +++ b/test/pull-request-target-has-tweet-issue-comment/test.js @@ -0,0 +1,91 @@ +/** + * This test checks the happy path of pull request adding a new *.tweet file + */ + +const nock = require("nock"); +const tap = require("tap"); + +// SETUP +process.env.GITHUB_EVENT_NAME = "pull_request_target"; +process.env.GITHUB_TOKEN = "secret123"; +process.env.GITHUB_EVENT_PATH = require.resolve("./event.json"); + +// set other env variables so action-toolkit is happy +process.env.GITHUB_REF = ""; +process.env.GITHUB_WORKSPACE = ""; +process.env.GITHUB_WORKFLOW = ""; +process.env.GITHUB_ACTION = "twitter-together"; +process.env.GITHUB_ACTOR = ""; +process.env.GITHUB_REPOSITORY = ""; +process.env.GITHUB_SHA = ""; + +// MOCK +nock("https://api.github.com", { + reqheaders: { + authorization: "token secret123", + }, +}) + // get changed files + .get("/repos/twitter-together/action/pulls/123/files") + .reply(200, [ + { + status: "added", + filename: "tweets/hello-world.tweet", + }, + ]); + +// get pull request diff +nock("https://api.github.com", { + reqheaders: { + accept: "application/vnd.github.diff", + authorization: "token secret123", + }, +}) + .get("/repos/twitter-together/action/pulls/123") + .reply( + 200, + `diff --git a/tweets/hello-world.tweet b/tweets/hello-world.tweet +new file mode 100644 +index 0000000..0123456 +--- /dev/null ++++ b/tweets/hello-world.tweet +@@ -0,0 +1 @@ ++Hello, world!` + ); + +// check for comments +nock("https://api.github.com", { + reqheaders: { + authorization: "token secret123", + }, +}) + // no comments + .get("/repos/twitter-together/action/issues/123/comments") + .reply(200, []); + +// post comment +nock("https://api.github.com") + // get changed files + .post("/repos/twitter-together/action/issues/123/comments", ({ body }) => { + tap.same( + body, + `## Found 1 new \`.tweet\` file(s) + +### ✅ Valid Tweet + +> Hello, world! + +--- + +*Preview using 0000000000000000000000000000000000000002 generated by [Twitter, together!](https://github.com/twitter-together/action)*` + ); + return true; + }) + .reply(201); + +process.on("exit", (code) => { + tap.equal(code, 0); + tap.same(nock.pendingMocks(), []); +}); + +require("../../lib"); diff --git a/test/push-main-tweet-error/test.js b/test/push-main-tweet-error/test.js index b18c14ef..2aff9068 100644 --- a/test/push-main-tweet-error/test.js +++ b/test/push-main-tweet-error/test.js @@ -45,7 +45,6 @@ nock("https://api.github.com", { .post( "/repos/twitter-together/action/commits/0000000000000000000000000000000000000002/comments", (body) => { - console.log(body.body); tap.equal( body.body, "Errors:\n\n- Tweet exceeds maximum length of 280 characters by 166 characters"