diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 9360c7f72c..c23eaeedcc 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -41,7 +41,7 @@ jobs: - run: yarn - run: yarn build:frontend - run: yarn lint - - run: yarn run test + - run: yarn run test:report # This adds test results as github check to the workflow env: CI: true TEST_DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres diff --git a/.github/workflows/build_prs.yaml b/.github/workflows/build_prs.yaml index db002794e3..e5de6e48f8 100644 --- a/.github/workflows/build_prs.yaml +++ b/.github/workflows/build_prs.yaml @@ -20,4 +20,3 @@ jobs: - run: yarn install --frozen-lockfile --ignore-scripts - run: yarn lint - run: yarn build:backend - #- run: yarn build:frontend # not needed diff --git a/.github/workflows/build_prs_test_report.yaml b/.github/workflows/build_prs_test_report.yaml new file mode 100644 index 0000000000..58183b2455 --- /dev/null +++ b/.github/workflows/build_prs_test_report.yaml @@ -0,0 +1,45 @@ +name: TestReport +on: + pull_request: + paths-ignore: + - website/** + - coverage/** + +jobs: + testreport: + runs-on: ubuntu-latest + services: + # Label used to access the service container + postgres: + # Docker Hub image + image: postgres + # Provide the password for postgres + env: + POSTGRES_PASSWORD: postgres + # Set health checks to wait until postgres has started + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js 18 + uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'yarn' + - run: yarn install --frozen-lockfile --ignore-scripts + - run: yarn build:backend + - run: yarn test:report + env: + CI: true + TEST_DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres + DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres + - name: Upload test report to build # Done this way since external PRs would not be able to write the check. See https://github.com/marketplace/actions/test-reporter#recommended-setup-for-public-repositories + uses: actions/upload-artifact@v3 + if: success() || failure() + with: + name: test-results + path: ./reports/jest-junit.xml diff --git a/.github/workflows/pr_add_test_results.yaml b/.github/workflows/pr_add_test_results.yaml new file mode 100644 index 0000000000..1af8e5ac13 --- /dev/null +++ b/.github/workflows/pr_add_test_results.yaml @@ -0,0 +1,16 @@ +name: 'Attach Test report' +on: + workflow_run: + workflows: [TestReport] + types: + - completed +jobs: + report: + runs-on: ubuntu-latest + steps: + - uses: dorny/test-reporter@v1 + with: + artifact: test-results # artifact name + name: Unit Tests # Name of the check run which will be created + path: '*.xml' # Path to test results (inside artifact .zip) + reporter: jest-junit diff --git a/.gitignore b/.gitignore index 35d5da243b..c286b7318f 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,4 @@ frontend/build # Generated docs website/docs/reference/api/**/sidebar.js website/docs/generated +reports/jest-junit.xml diff --git a/package.json b/package.json index b7aca5a494..cbc9d84daf 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "test": "NODE_ENV=test PORT=4243 jest", "test:unit": "NODE_ENV=test PORT=4243 jest --testPathIgnorePatterns=src/test/e2e --testPathIgnorePatterns=dist", "test:docker": "./scripts/docker-postgres.sh", + "test:report": "NODE_ENV=test PORT=4243 jest --reporters=\"default\" --reporters=\"jest-junit\"", "test:docker:cleanup": "docker rm -f unleash-postgres", "test:watch": "yarn test --watch", "test:coverage": "NODE_ENV=test PORT=4243 jest --coverage --testLocationInResults --outputFile=\"coverage/report.json\" --forceExit --testTimeout=10000", @@ -61,6 +62,16 @@ "preversion": "./scripts/check-release.sh", "heroku-postbuild": "cd frontend && yarn && yarn build" }, + "jest-junit": { + "suiteName": "Unleash Unit Tests", + "outputDirectory": "./reports", + "outputName": "jest-junit.xml", + "uniqueOutputName": "false", + "classNameTemplate": "{classname}-{title}", + "titleTemplate": "{classname}-{title}", + "ancestorSeparator": " › ", + "usePathForSuiteName": "true" + }, "jest": { "automock": false, "maxWorkers": 4, @@ -193,6 +204,7 @@ "fetch-mock": "9.11.0", "husky": "8.0.3", "jest": "29.5.0", + "jest-junit": "^16.0.0", "lint-staged": "13.2.2", "nock": "13.3.1", "openapi-enforcer": "1.22.3", diff --git a/src/lib/__snapshots__/create-config.test.ts.snap b/src/lib/__snapshots__/create-config.test.ts.snap index 03bcaee127..22c257eae2 100644 --- a/src/lib/__snapshots__/create-config.test.ts.snap +++ b/src/lib/__snapshots__/create-config.test.ts.snap @@ -149,6 +149,7 @@ exports[`should create default config 1`] = ` "preHook": undefined, "preRouterHook": undefined, "prometheusApi": undefined, + "publicFolder": undefined, "secureHeaders": false, "segmentValuesLimit": 1000, "server": { diff --git a/src/lib/app.ts b/src/lib/app.ts index 662ccf57f2..32db3d0266 100644 --- a/src/lib/app.ts +++ b/src/lib/app.ts @@ -40,7 +40,7 @@ export default async function getApp( const app = express(); const baseUriPath = config.server.baseUriPath || ''; - const publicFolder = findPublicFolder(); + const publicFolder = config.publicFolder || findPublicFolder(); let indexHTML = await loadIndexHTML(config, publicFolder); app.set('trust proxy', true); diff --git a/src/lib/create-config.ts b/src/lib/create-config.ts index 0aacf5efe3..d07a0615fb 100644 --- a/src/lib/create-config.ts +++ b/src/lib/create-config.ts @@ -497,6 +497,7 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig { clientFeatureCaching, accessControlMaxAge, prometheusApi, + publicFolder: options.publicFolder, }; } diff --git a/src/lib/types/option.ts b/src/lib/types/option.ts index bd25be6414..644a3e8d7f 100644 --- a/src/lib/types/option.ts +++ b/src/lib/types/option.ts @@ -116,6 +116,7 @@ export interface IUnleashOptions { flagResolver?: IFlagResolver; accessControlMaxAge?: number; prometheusApi?: string; + publicFolder?: string; } export interface IEmailOption { @@ -203,4 +204,5 @@ export interface IUnleashConfig { clientFeatureCaching: IClientCachingOption; accessControlMaxAge: number; prometheusApi?: string; + publicFolder?: string; } diff --git a/src/lib/util/load-index-html.ts b/src/lib/util/load-index-html.ts index 9f732c379a..865162af0e 100644 --- a/src/lib/util/load-index-html.ts +++ b/src/lib/util/load-index-html.ts @@ -17,7 +17,9 @@ export async function loadIndexHTML( indexHTML = await res.text(); } else { indexHTML = fs - .readFileSync(path.join(publicFolder, 'index.html')) + .readFileSync( + path.join(config.publicFolder || publicFolder, 'index.html'), + ) .toString(); } diff --git a/src/lib/util/rewriteHTML.test.ts b/src/lib/util/rewriteHTML.test.ts index b40f30ce6c..172e3c0020 100644 --- a/src/lib/util/rewriteHTML.test.ts +++ b/src/lib/util/rewriteHTML.test.ts @@ -1,10 +1,9 @@ import fs from 'fs'; import path from 'path'; import { rewriteHTML } from './rewriteHTML'; -import { findPublicFolder } from './findPublicFolder'; const input = fs - .readFileSync(path.join(findPublicFolder(), 'index.html')) + .readFileSync(path.join(__dirname, '../../test/examples', 'index.html')) .toString(); test('rewriteHTML substitutes meta tag with existing rewrite value', () => { diff --git a/src/lib/util/rewriteHTML.ts b/src/lib/util/rewriteHTML.ts index 6f05dd1e14..58de0c11c4 100644 --- a/src/lib/util/rewriteHTML.ts +++ b/src/lib/util/rewriteHTML.ts @@ -11,7 +11,7 @@ export const rewriteHTML = ( const faviconPrefix = cdnPrefix ? 'https://cdn.getunleash.io' : ''; result = result.replace(/::faviconPrefix::/gi, faviconPrefix); - result = result.replace(/::uiFlags::/gi, uiFlags); + result = result.replace(/::uiFlags::/gi, uiFlags || '{}'); result = result.replace( /\/static/gi, diff --git a/src/test/config/test-config.ts b/src/test/config/test-config.ts index 20a83507a0..ccf9bd9b12 100644 --- a/src/test/config/test-config.ts +++ b/src/test/config/test-config.ts @@ -6,6 +6,7 @@ import { } from '../../lib/types/option'; import getLogger from '../fixtures/no-logger'; import { createConfig } from '../../lib/create-config'; +import path from 'path'; function mergeAll(objects: Partial[]): T { return merge.all(objects.filter((i) => i)); @@ -28,7 +29,8 @@ export function createTestConfig(config?: IUnleashOptions): IUnleashConfig { embedProxyFrontend: true, }, }, + publicFolder: path.join(__dirname, '../examples'), }; - const options = mergeAll([testConfig, config]); + const options = mergeAll([testConfig, config || {}]); return createConfig(options); } diff --git a/src/test/e2e/services/group-service.e2e.test.ts b/src/test/e2e/services/group-service.e2e.test.ts index 5048df20da..9c4d1e4332 100644 --- a/src/test/e2e/services/group-service.e2e.test.ts +++ b/src/test/e2e/services/group-service.e2e.test.ts @@ -104,7 +104,7 @@ test('adding a root role to a group with a project role should fail', async () = description: 'root_group', }); - stores.accessStore.addGroupToRole(group.id, 1, 'test', 'default'); + await stores.accessStore.addGroupToRole(group.id, 1, 'test', 'default'); await expect(() => { return groupService.updateGroup( diff --git a/src/test/examples/favicon.ico b/src/test/examples/favicon.ico new file mode 100644 index 0000000000..551d158eee Binary files /dev/null and b/src/test/examples/favicon.ico differ diff --git a/src/test/examples/index.html b/src/test/examples/index.html new file mode 100644 index 0000000000..e3c7d02cda --- /dev/null +++ b/src/test/examples/index.html @@ -0,0 +1,26 @@ + + + + + + + + + + + + Unleash + + + + + + + +
+ + + diff --git a/yarn.lock b/yarn.lock index 17a4d57ee1..0244c0dd80 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4466,6 +4466,16 @@ jest-haste-map@^29.5.0: optionalDependencies: fsevents "^2.3.2" +jest-junit@^16.0.0: + version "16.0.0" + resolved "https://registry.yarnpkg.com/jest-junit/-/jest-junit-16.0.0.tgz#d838e8c561cf9fdd7eb54f63020777eee4136785" + integrity sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ== + dependencies: + mkdirp "^1.0.4" + strip-ansi "^6.0.1" + uuid "^8.3.2" + xml "^1.0.1" + jest-leak-detector@^29.5.0: version "29.5.0" resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz#cf4bdea9615c72bac4a3a7ba7e7930f9c0610c8c" @@ -7322,6 +7332,11 @@ uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + uuid@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5" @@ -7485,6 +7500,11 @@ write-file-atomic@^4.0.2: imurmurhash "^0.1.4" signal-exit "^3.0.7" +xml@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" + integrity sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw== + xtend@^4.0.0, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"