From 61dd6c04433a4154b82d6031e5151943349f5673 Mon Sep 17 00:00:00 2001
From: chris48s <chris48s@users.noreply.github.com>
Date: Thu, 15 Dec 2022 18:36:17 +0000
Subject: [PATCH] switch from github workflows to github actions workflows;
 test [githubactionsworkflowstatus githubworkflowstatus] (#8475)

* switch from github workflows to github actions workflows

* update github actions workflow badge to use api

* add test case for missing branch param

* custom deprecation message
---
 .../github-actions-workflow-status.service.js | 101 ++++++++++++++++
 .../github-actions-workflow-status.tester.js  |  59 ++++++++++
 .../github/github-workflow-status.service.js  | 110 +++---------------
 .../github/github-workflow-status.tester.js   |  33 +++---
 4 files changed, 195 insertions(+), 108 deletions(-)
 create mode 100644 services/github/github-actions-workflow-status.service.js
 create mode 100644 services/github/github-actions-workflow-status.tester.js

diff --git a/services/github/github-actions-workflow-status.service.js b/services/github/github-actions-workflow-status.service.js
new file mode 100644
index 0000000000..2f18965895
--- /dev/null
+++ b/services/github/github-actions-workflow-status.service.js
@@ -0,0 +1,101 @@
+import Joi from 'joi'
+import { isBuildStatus, renderBuildStatusBadge } from '../build-status.js'
+import { NotFound } from '../index.js'
+import { GithubAuthV3Service } from './github-auth-service.js'
+import { documentation, errorMessagesFor } from './github-helpers.js'
+
+const schema = Joi.object({
+  workflow_runs: Joi.array()
+    .items(
+      Joi.object({
+        conclusion: Joi.alternatives()
+          .try(isBuildStatus, Joi.equal('no status'))
+          .required(),
+      })
+    )
+    .required()
+    .min(0)
+    .max(1),
+}).required()
+
+const queryParamSchema = Joi.object({
+  event: Joi.string(),
+  branch: Joi.string().required(),
+}).required()
+
+const keywords = ['action', 'actions']
+
+export default class GithubActionsWorkflowStatus extends GithubAuthV3Service {
+  static category = 'build'
+
+  static route = {
+    base: 'github/actions/workflow/status',
+    pattern: ':user/:repo/:workflow+',
+    queryParamSchema,
+  }
+
+  static examples = [
+    {
+      title: 'GitHub Workflow Status',
+      namedParams: {
+        user: 'actions',
+        repo: 'toolkit',
+        workflow: 'unit-tests.yml',
+      },
+      queryParams: {
+        branch: 'main',
+      },
+      staticPreview: renderBuildStatusBadge({
+        status: 'passing',
+      }),
+      documentation,
+      keywords,
+    },
+    {
+      title: 'GitHub Workflow Status (with event)',
+      namedParams: {
+        user: 'actions',
+        repo: 'toolkit',
+        workflow: 'unit-tests.yml',
+      },
+      queryParams: {
+        event: 'push',
+        branch: 'main',
+      },
+      staticPreview: renderBuildStatusBadge({
+        status: 'passing',
+      }),
+      documentation,
+      keywords,
+    },
+  ]
+
+  static defaultBadgeData = {
+    label: 'build',
+  }
+
+  async fetch({ user, repo, workflow, branch, event }) {
+    return await this._requestJson({
+      schema,
+      url: `/repos/${user}/${repo}/actions/workflows/${workflow}/runs`,
+      options: {
+        searchParams: {
+          branch,
+          event,
+          page: '1',
+          per_page: '1',
+          exclude_pull_requests: 'true',
+        },
+      },
+      errorMessages: errorMessagesFor('repo or workflow not found'),
+    })
+  }
+
+  async handle({ user, repo, workflow }, { branch, event }) {
+    const data = await this.fetch({ user, repo, workflow, branch, event })
+    if (data.workflow_runs.length === 0) {
+      throw new NotFound({ prettyMessage: 'branch or event not found' })
+    }
+    return renderBuildStatusBadge({ status: data.workflow_runs[0].conclusion })
+  }
+}
diff --git a/services/github/github-actions-workflow-status.tester.js b/services/github/github-actions-workflow-status.tester.js
new file mode 100644
index 0000000000..9ebc19e4a6
--- /dev/null
+++ b/services/github/github-actions-workflow-status.tester.js
@@ -0,0 +1,59 @@
+import Joi from 'joi'
+import { isBuildStatus } from '../build-status.js'
+import { createServiceTester } from '../tester.js'
+export const t = await createServiceTester()
+
+const isWorkflowStatus = Joi.alternatives()
+  .try(isBuildStatus, Joi.equal('no status'))
+  .required()
+
+t.create('missing branch param')
+  .get('/actions/toolkit/unit-tests.yml.json')
+  .expectBadge({
+    label: 'build',
+    message: 'invalid query parameter: branch',
+  })
+
+t.create('nonexistent repo')
+  .get('/badges/shields-fakeness/fake.yml.json?branch=main')
+  .expectBadge({
+    label: 'build',
+    message: 'repo or workflow not found',
+  })
+
+t.create('nonexistent workflow')
+  .get('/actions/toolkit/not-a-real-workflow.yml.json?branch=main')
+  .expectBadge({
+    label: 'build',
+    message: 'repo or workflow not found',
+  })
+
+t.create('nonexistent branch')
+  .get('/actions/toolkit/unit-tests.yml.json?branch=not-a-real-branch')
+  .expectBadge({
+    label: 'build',
+    message: 'branch or event not found',
+  })
+
+t.create('nonexistent event')
+  .get(
+    '/actions/toolkit/unit-tests.yml.json?branch=main&event=not-a-real-event'
+  )
+  .expectBadge({
+    label: 'build',
+    message: 'branch or event not found',
+  })
+
+t.create('valid workflow')
+  .get('/actions/toolkit/unit-tests.yml.json?branch=main')
+  .expectBadge({
+    label: 'build',
+    message: isWorkflowStatus,
+  })
+
+t.create('valid workflow (with event)')
+  .get('/actions/toolkit/unit-tests.yml.json?branch=main&event=push')
+  .expectBadge({
+    label: 'build',
+    message: isWorkflowStatus,
+  })
diff --git a/services/github/github-workflow-status.service.js b/services/github/github-workflow-status.service.js
index d1d47ca0bb..69e6e8f286 100644
--- a/services/github/github-workflow-status.service.js
+++ b/services/github/github-workflow-status.service.js
@@ -1,100 +1,28 @@
-import Joi from 'joi'
-import { isBuildStatus, renderBuildStatusBadge } from '../build-status.js'
-import { BaseSvgScrapingService } from '../index.js'
-import { documentation } from './github-helpers.js'
+import { BaseService } from '../index.js'
 
-const schema = Joi.object({
-  message: Joi.alternatives()
-    .try(isBuildStatus, Joi.equal('no status'))
-    .required(),
-}).required()
-
-const queryParamSchema = Joi.object({
-  event: Joi.string(),
-}).required()
-
-const keywords = ['action', 'actions']
-
-export default class GithubWorkflowStatus extends BaseSvgScrapingService {
+export default class DeprecatedGithubWorkflowStatus extends BaseService {
   static category = 'build'
 
   static route = {
     base: 'github/workflow/status',
-    pattern: ':user/:repo/:workflow/:branch*',
-    queryParamSchema,
-  }
-
-  static examples = [
-    {
-      title: 'GitHub Workflow Status',
-      pattern: ':user/:repo/:workflow',
-      namedParams: {
-        user: 'actions',
-        repo: 'toolkit',
-        workflow: 'toolkit-unit-tests',
-      },
-      staticPreview: renderBuildStatusBadge({
-        status: 'passing',
-      }),
-      documentation,
-      keywords,
-    },
-    {
-      title: 'GitHub Workflow Status (branch)',
-      pattern: ':user/:repo/:workflow/:branch',
-      namedParams: {
-        user: 'actions',
-        repo: 'toolkit',
-        workflow: 'toolkit-unit-tests',
-        branch: 'master',
-      },
-      staticPreview: renderBuildStatusBadge({
-        status: 'passing',
-      }),
-      documentation,
-      keywords,
-    },
-    {
-      title: 'GitHub Workflow Status (event)',
-      pattern: ':user/:repo/:workflow',
-      namedParams: {
-        user: 'actions',
-        repo: 'toolkit',
-        workflow: 'toolkit-unit-tests',
-      },
-      queryParams: {
-        event: 'push',
-      },
-      staticPreview: renderBuildStatusBadge({
-        status: 'passing',
-      }),
-      documentation,
-      keywords,
-    },
-  ]
-
-  static defaultBadgeData = {
-    label: 'build',
-  }
-
-  async fetch({ user, repo, workflow, branch, event }) {
-    const { message: status } = await this._requestSvg({
-      schema,
-      url: `https://github.com/${user}/${repo}/workflows/${encodeURIComponent(
-        workflow
-      )}/badge.svg`,
-      options: { searchParams: { branch, event } },
-      valueMatcher: />([^<>]+)<\/tspan><\/text><\/g><path/,
-      errorMessages: {
-        404: 'repo, branch, or workflow not found',
-      },
-    })
-
-    return { status }
+    pattern: ':various+',
   }
 
-  async handle({ user, repo, workflow, branch }, { event }) {
-    const { status } = await this.fetch({ user, repo, workflow, branch, event })
-    return renderBuildStatusBadge({ status })
+  static examples = []
+
+  static defaultBadgeData = { label: 'build' }
+
+  async handle() {
+    return {
+      label: 'build',
+      message: 'https://github.com/badges/shields/issues/8671',
+      /*
+      This is a 'special' deprecation because we are making a breaking change
+      We've implemented it as a custom class instead of a normal
+      deprecatedService so that we can include link.
+      */
+      link: ['https://github.com/badges/shields/issues/8671'],
+      color: 'red',
+    }
   }
 }
diff --git a/services/github/github-workflow-status.tester.js b/services/github/github-workflow-status.tester.js
index 573a16e7b1..de27de37aa 100644
--- a/services/github/github-workflow-status.tester.js
+++ b/services/github/github-workflow-status.tester.js
@@ -1,43 +1,42 @@
-import Joi from 'joi'
-import { isBuildStatus } from '../build-status.js'
-import { createServiceTester } from '../tester.js'
-export const t = await createServiceTester()
+import { ServiceTester } from '../tester.js'
 
-const isWorkflowStatus = Joi.alternatives()
-  .try(isBuildStatus, Joi.equal('no status'))
-  .required()
+export const t = new ServiceTester({
+  id: 'GithubWorkflowStatus',
+  title: 'Github Workflow Status',
+  pathPrefix: '/github/workflow/status',
+})
 
-t.create('nonexistent repo')
+t.create('no longer available (previously nonexistent repo)')
   .get('/badges/shields-fakeness/fake.json')
   .expectBadge({
     label: 'build',
-    message: 'repo, branch, or workflow not found',
+    message: 'https://github.com/badges/shields/issues/8671',
   })
 
-t.create('nonexistent workflow')
+t.create('no longer available (previously nonexistent workflow)')
   .get('/actions/toolkit/not-a-real-workflow.json')
   .expectBadge({
     label: 'build',
-    message: 'repo, branch, or workflow not found',
+    message: 'https://github.com/badges/shields/issues/8671',
   })
 
-t.create('valid workflow')
+t.create('no longer available (previously valid workflow)')
   .get('/actions/toolkit/toolkit-unit-tests.json')
   .expectBadge({
     label: 'build',
-    message: isWorkflowStatus,
+    message: 'https://github.com/badges/shields/issues/8671',
   })
 
-t.create('valid workflow (branch)')
+t.create('no longer available (previously valid workflow - branch)')
   .get('/actions/toolkit/toolkit-unit-tests/master.json')
   .expectBadge({
     label: 'build',
-    message: isWorkflowStatus,
+    message: 'https://github.com/badges/shields/issues/8671',
   })
 
-t.create('valid workflow (event)')
+t.create('no longer available (previously valid workflow - event)')
   .get('/actions/toolkit/toolkit-unit-tests.json?event=push')
   .expectBadge({
     label: 'build',
-    message: isWorkflowStatus,
+    message: 'https://github.com/badges/shields/issues/8671',
   })
-- 
GitLab