diff --git a/.ibm/pipelines/resources/config_map/configmap-app-config-rhdh.yaml b/.ibm/pipelines/resources/config_map/configmap-app-config-rhdh.yaml index 6100fb910..3a07b6e3d 100644 --- a/.ibm/pipelines/resources/config_map/configmap-app-config-rhdh.yaml +++ b/.ibm/pipelines/resources/config_map/configmap-app-config-rhdh.yaml @@ -135,6 +135,12 @@ data: rules: - allow: [User, Group] providers: + github: + providerId: + organization: '${GITHUB_ORG}' + schedule: + frequency: { minutes: 30} + timeout: { minutes: 30} githubOrg: id: production githubUrl: "${GITHUB_URL}" diff --git a/e2e-tests/playwright/e2e/github-discovery.spec.ts b/e2e-tests/playwright/e2e/github-discovery.spec.ts new file mode 100644 index 000000000..e9e9c20ad --- /dev/null +++ b/e2e-tests/playwright/e2e/github-discovery.spec.ts @@ -0,0 +1,48 @@ +import { test as base } from '@playwright/test'; +import { Catalog } from '../support/pages/Catalog'; +import GithubApi from '../support/api/github'; +import { CATALOG_FILE, JANUS_QE_ORG } from '../utils/constants'; +import { Common } from '../utils/Common'; +import { assert } from 'console'; + +type GithubDiscoveryFixture = { + catalogPage: Catalog; + githubApi: GithubApi; + testOrganization: string; +}; + +const test = base.extend({ + catalogPage: async ({ page }, use) => { + await new Common(page).loginAsGithubUser(); + const catalog = new Catalog(page); + await catalog.go(); + use(catalog); + }, + githubApi: new GithubApi(), + testOrganization: JANUS_QE_ORG, +}); + +test.describe('Github Discovery Catalog', () => { + test(`Discover Organization's Catalog`, async ({ + catalogPage, + githubApi, + testOrganization, + }) => { + const organizationRepos = await githubApi.getReposFromOrg(testOrganization); + const reposNames: string[] = organizationRepos.map(repo => repo['name']); + const realComponents: string[] = reposNames.filter( + async repo => + await githubApi.fileExistsOnRepo( + `${testOrganization}/${repo}`, + CATALOG_FILE, + ), + ); + + for (let i = 0; i != realComponents.length; i++) { + const repo = realComponents[i]; + await catalogPage.search(repo); + const row = await catalogPage.tableRow(repo); + assert(await row.isVisible()); + } + }); +}); diff --git a/e2e-tests/playwright/support/api/github-structures.ts b/e2e-tests/playwright/support/api/github-structures.ts new file mode 100644 index 000000000..69b5faa2d --- /dev/null +++ b/e2e-tests/playwright/support/api/github-structures.ts @@ -0,0 +1,15 @@ +export class getOrganizationResponse { + reposUrl: string; + constructor(response: any) { + enum OrganizationResponseAttributes { + ReposUrl = 'repos_url', + } + this.reposUrl = response[OrganizationResponseAttributes.ReposUrl]; + } +} + +export enum ItemStatus { + open = 'open', + closed = 'closed', + all = 'all', +} diff --git a/e2e-tests/playwright/support/api/github.ts b/e2e-tests/playwright/support/api/github.ts new file mode 100644 index 000000000..6944cdb20 --- /dev/null +++ b/e2e-tests/playwright/support/api/github.ts @@ -0,0 +1,71 @@ +import { getOrganizationResponse } from './github-structures'; +import { JANUS_ORG } from '../../utils/constants'; +import { APIResponse, request } from '@playwright/test'; + +// https://docs.github.com/en/rest?apiVersion=2022-11-28 +export default class GithubApi { + private static API_URL = 'https://api.github.com'; + private static API_VERSION = '2022-11-28'; + private static AUTH_HEADER = { + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${process.env.GH_RHDH_QE_USER_TOKEN}`, + 'X-GitHub-Api-Version': GithubApi.API_VERSION, + }; + + public async getOrganization( + org = JANUS_ORG, + ): Promise { + const req = await this._organization(org).get(); + return new getOrganizationResponse(req.json()); + } + + public async getReposFromOrg(org = JANUS_ORG) { + const req = await this._organization(org).repos(); + return req.json(); + } + + public async fileExistsOnRepo(repo: string, file: string): Promise { + const req = await this._repo(repo).getContent(file); + const status = req.status(); + if (status == 403) { + throw Error('You don-t have permissions to see this path'); + } + return [200, 302, 304].includes(status); + } + + private _myContext = request.newContext({ + baseURL: GithubApi.API_URL, + extraHTTPHeaders: GithubApi.AUTH_HEADER, + }); + + private _repo(repo: string) { + const url = `/repos/${repo}/`; + return { + getContent: async (path: string) => { + path = url + path; + const context = await this._myContext; + return context.get(path); + }, + }; + } + + private _organization(organization: string) { + const url = '/orgs/'; + + return { + get: async (): Promise => { + const path: string = url + organization; + const context = await this._myContext; + return context.get(path); + }, + + repos: async (): Promise => { + const context = await this._myContext; + const organizationResponse = await new GithubApi() + ._organization(organization) + .get(); + return context.get((await organizationResponse.json()).repos_url); + }, + }; + } +} diff --git a/e2e-tests/playwright/support/pages/Catalog.ts b/e2e-tests/playwright/support/pages/Catalog.ts index feb972094..8d6ba4101 100644 --- a/e2e-tests/playwright/support/pages/Catalog.ts +++ b/e2e-tests/playwright/support/pages/Catalog.ts @@ -1,14 +1,21 @@ -import { Page } from '@playwright/test'; +import { Locator, Page } from '@playwright/test'; import { UIhelper } from '../../utils/UIhelper'; +import playwrightConfig from '../../../playwright.config'; //${BASE_URL}/catalog page export class Catalog { private page: Page; private uiHelper: UIhelper; + private searchField: Locator; constructor(page: Page) { this.page = page; this.uiHelper = new UIhelper(page); + this.searchField = page.locator('#input-with-icon-adornment'); + } + + async go() { + await this.uiHelper.openSidebar('Catalog'); } async goToBackstageJanusProjectCITab() { @@ -23,4 +30,19 @@ export class Catalog { await this.uiHelper.clickByDataTestId('user-picker-all'); await this.uiHelper.clickLink('backstage-janus'); } + + async search(s: string) { + await this.searchField.clear(); + const searchResponse = this.page.waitForResponse( + new RegExp( + `${playwrightConfig.use.baseURL}/api/catalog/entities/by-query/*`, + ), + ); + await this.searchField.fill(s); + await searchResponse; + } + + async tableRow(content: string) { + return this.page.locator(`tr >> a >> text="${content}"`); + } } diff --git a/e2e-tests/playwright/support/pages/catalog-item.ts b/e2e-tests/playwright/support/pages/catalog-item.ts new file mode 100644 index 000000000..ff7e7ba5c --- /dev/null +++ b/e2e-tests/playwright/support/pages/catalog-item.ts @@ -0,0 +1,20 @@ +import { expect, Page } from '@playwright/test'; +import { GITHUB_URL } from '../../utils/constants'; + +export class CatalogItem { + private page: Page; + + githubLink = (path: string): string => { + return `a[href*="${GITHUB_URL}${path}"]`; + }; + + constructor(page: Page) { + this.page = page; + } + + async validateGithubLink(s: string) { + const url = this.githubLink(s); + const link = this.page.locator(url).first(); + await expect(link).toBeVisible(); + } +} diff --git a/e2e-tests/playwright/utils/constants.ts b/e2e-tests/playwright/utils/constants.ts new file mode 100644 index 000000000..f4780ff94 --- /dev/null +++ b/e2e-tests/playwright/utils/constants.ts @@ -0,0 +1,5 @@ +export const GITHUB_URL = 'https://github.com/'; +export const JANUS_ORG = 'janus-idp'; +export const JANUS_QE_ORG = 'janus-qe'; +export const SHOWCASE_REPO = `${JANUS_ORG}/backstage-showcase`; +export const CATALOG_FILE = 'catalog-info.yaml';