Merge branch 'master' into feat/rspack
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-defaults': patch
|
||||
---
|
||||
|
||||
Sensitive internal fields on `BackstageCredentials` objects are now defined as read-only properties in order to minimize risk of leakage.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-test-utils': patch
|
||||
---
|
||||
|
||||
Minor doc string changes
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
'@backstage/backend-defaults': patch
|
||||
---
|
||||
|
||||
The default root HTTP service implementation will now pretty-print JSON responses in development.
|
||||
|
||||
If you are overriding the `rootHttpRouterServiceFactory` with a `configure` function that doesn't call `applyDefaults`, you can introduce this functionality by adding the following snippet inside `configure`:
|
||||
|
||||
```ts
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
app.set('json spaces', 2);
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
'@backstage/test-utils': minor
|
||||
'@backstage/frontend-test-utils': patch
|
||||
---
|
||||
|
||||
Added a `mockApis` export, which will replace the `MockX` API implementation classes and their related types. This is analogous with the backend's `mockServices`.
|
||||
|
||||
**DEPRECATED** several old helpers:
|
||||
|
||||
- Deprecated `MockAnalyticsApi`, please use `mockApis.analytics` instead.
|
||||
- Deprecated `MockConfigApi`, please use `mockApis.config` instead.
|
||||
- Deprecated `MockPermissionApi`, please use `mockApis.permission` instead.
|
||||
- Deprecated `MockStorageApi`, please use `mockApis.storage` instead.
|
||||
- Deprecated `MockTranslationApi`, please use `mockApis.translation` instead.
|
||||
@@ -90,7 +90,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
|
||||
# Identify comment to be updated
|
||||
- name: Find comment for API Changes
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
with:
|
||||
# Fetch the commit that's merged into the base rather than the target ref
|
||||
# This will let us diff only the contents of the PR, without fetching more history
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
cat ${{ github.event_path }} > event.json
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
|
||||
with:
|
||||
name: preview-spec
|
||||
path: |
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
with:
|
||||
# Fetch the commit that's merged into the base rather than the target ref
|
||||
# This will let us diff only the contents of the PR, without fetching more history
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
with:
|
||||
ref: '${{ github.event.pull_request.merge_commit_sha }}'
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
|
||||
- name: use node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
|
||||
- name: use node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
@@ -209,7 +209,7 @@ jobs:
|
||||
INTEGRATION_TEST_AZURE_TOKEN: ${{ secrets.INTEGRATION_TEST_AZURE_TOKEN }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
- name: fetch master branch
|
||||
run: git fetch origin master
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: checkout
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
with:
|
||||
path: backstage
|
||||
ref: ${{ github.event.client_payload.version && env.RELEASE_VERSION || github.ref }}
|
||||
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
result-encoding: string
|
||||
|
||||
- name: checkout latest release
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
with:
|
||||
ref: refs/tags/${{ steps.find-release.outputs.result }}
|
||||
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
run: yarn build:api-docs
|
||||
|
||||
- name: upload API reference
|
||||
uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
|
||||
with:
|
||||
name: stable-reference
|
||||
path: docs/reference/
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: checkout master
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
|
||||
- name: use node.js 18.x
|
||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
run: yarn build:api-docs
|
||||
|
||||
- name: upload API reference
|
||||
uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
|
||||
with:
|
||||
name: next-reference
|
||||
path: docs/reference/
|
||||
@@ -130,7 +130,7 @@ jobs:
|
||||
working-directory: storybook
|
||||
|
||||
- name: storybook upload
|
||||
uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
|
||||
with:
|
||||
name: storybook
|
||||
path: storybook/dist/
|
||||
@@ -170,7 +170,7 @@ jobs:
|
||||
|
||||
# Stable docs
|
||||
- name: checkout latest release
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
with:
|
||||
ref: refs/tags/${{ needs.stable.outputs.release }}
|
||||
|
||||
@@ -198,7 +198,7 @@ jobs:
|
||||
|
||||
# Next docs
|
||||
- name: checkout master
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
with:
|
||||
clean: false
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
INTEGRATION_TEST_AZURE_TOKEN: ${{ secrets.INTEGRATION_TEST_AZURE_TOKEN }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
|
||||
- name: use node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: 'Checkout code'
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
with:
|
||||
# Fetch changes to previous commit - required for 'only_changed' in Prettier action
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
with:
|
||||
fetch-depth: 2
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
with:
|
||||
# 'v' prefix is added here for the tag, we keep it out of the manifest logic
|
||||
ref: v${{ github.event.client_payload.version }}
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
|
||||
# Checkout backstage/versions into /backstage/versions, which is where store the output
|
||||
- name: Checkout versions
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
with:
|
||||
repository: backstage/versions
|
||||
path: versions
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
with:
|
||||
fetch-depth: 2
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
|
||||
- name: use node.js 18.x
|
||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
- name: Monitor and Synchronize Snyk Policies
|
||||
uses: snyk/actions/node@cdb760004ba9ea4d525f2e043745dfe85bb9077e # master
|
||||
with:
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
with:
|
||||
fetch-depth: 20000
|
||||
fetch-tags: true
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
with:
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
|
||||
# Vale does not support file excludes, so we use the script to generate a list of files instead
|
||||
# The action also does not allow args or a local config file to be passed in, so the files array
|
||||
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
- uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0
|
||||
with:
|
||||
python-version: '3.9'
|
||||
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
git config --global core.autocrlf false
|
||||
git config --global core.eol lf
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
|
||||
- name: Install Fossa
|
||||
run: "curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | bash"
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
|
||||
- name: use node.js 18.x
|
||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
with:
|
||||
fetch-depth: 0 # Required to retrieve git history
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
|
||||
- name: use node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
|
||||
|
||||
@@ -59,7 +59,7 @@ const backend = createBackend();
|
||||
|
||||
backend.add(
|
||||
rootHttpRouterServiceFactory({
|
||||
configure: ({ app, middleware, routes, config, logger, lifecycle }) => {
|
||||
configure: ({ app, middleware, routes, config, logger, healthRouter }) => {
|
||||
// Refer to https://expressjs.com/en/guide/writing-middleware.html on how to write express middleware
|
||||
const customMiddleware = {
|
||||
logging(): RequestHandler {
|
||||
@@ -88,11 +88,18 @@ backend.add(
|
||||
},
|
||||
};
|
||||
|
||||
// The default implementation pretty-prints JSON responses in development
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
app.set('json spaces', 2);
|
||||
}
|
||||
|
||||
// the built in middleware is provided through an option in the configure function
|
||||
app.use(middleware.helmet());
|
||||
app.use(middleware.cors());
|
||||
app.use(middleware.compression());
|
||||
|
||||
app.use(healthRouter);
|
||||
|
||||
// you can add you your own middleware in here
|
||||
app.use(customMiddleware.logging());
|
||||
|
||||
|
||||
@@ -364,22 +364,19 @@ load performance.
|
||||
|
||||
### Backend Development
|
||||
|
||||
The backend development bundling is also based on Webpack, but rather than
|
||||
starting up a web server, the backend is started up using the
|
||||
[`RunScriptWebpackPlugin`](https://www.npmjs.com/package/run-script-webpack-plugin).
|
||||
The reason for using Webpack for development of the backend is both that it is a
|
||||
convenient way to handle transpilation of a large set of packages, as well us
|
||||
allowing us to use hot module replacement and maintaining state while reloading
|
||||
individual backend modules. This is particularly useful when running the backend
|
||||
with in-memory SQLite as the database choice.
|
||||
The backend development setup does not use any bundling process. It runs a
|
||||
Node.js process directly, with on-the-fly transpilation from TypeScript to
|
||||
JavaScript. The transpilation is done with a custom transform based on
|
||||
[SWC](https://swc.rs/).
|
||||
|
||||
Except for executing in Node.js rather than a web server, the backend
|
||||
development bundling configuration is quite similar to the frontend one. It
|
||||
shares most of the Webpack configuration, including the transpilation setup.
|
||||
Some differences are that it does not inject any environment variables or node
|
||||
module fallbacks, and it uses
|
||||
[`webpack-node-externals`](https://www.npmjs.com/package/webpack-node-externals)
|
||||
to avoid bundling in dependency modules.
|
||||
During development the backend Node.js process will restart whenever there is a
|
||||
change to the source code. This means that any in-memory data will be lost. In
|
||||
order to store data between restarts, the backend process has an IPC channel
|
||||
available to store and restore data from the parent CLI process. The primary
|
||||
purpose of this, which is already built-in, is to restore the contents of
|
||||
databases when using SQLite for development. You can also use it for your own
|
||||
purposes, with the `DevDataStore` utility exported from the
|
||||
`@backstage/backend-dev-utils` package.
|
||||
|
||||
If you want to inspect the running Node.js process, the `--inspect` and
|
||||
`--inspect-brk` flags can be used, as they will be passed through as options to
|
||||
|
||||
@@ -7,7 +7,7 @@ description: Finding performance bottlenecks in your Backstage application
|
||||
Profiling can help you find performance bottlenecks in your code. This guide will show you how to profile
|
||||
both the backend and frontend of your Backstage application.
|
||||
|
||||
# Backend
|
||||
## Backend
|
||||
|
||||
To profile the backend, start the backend with the `--inspect` flag:
|
||||
|
||||
@@ -27,7 +27,7 @@ You can also use the `Memory` tab to profile the backend's memory usage and find
|
||||
|
||||
It's recommended to start profiling with short periods of time to avoid too much data being collected.
|
||||
|
||||
## Stress testing
|
||||
### Stress testing
|
||||
|
||||
To get more out of profiling, you might want to introduce additional load to your application with some tooling.
|
||||
One such tool is called [AutoCannon](https://www.npmjs.com/package/autocannon) which can be used to stress test the
|
||||
@@ -55,7 +55,7 @@ autocannon -H "Authorization=Bearer autocannon12345" http://localhost:7007/api/c
|
||||
|
||||
See more command options in the AutoCannon documentation.
|
||||
|
||||
# Frontend
|
||||
## Frontend
|
||||
|
||||
Profiling the frontend can be done by using the `React DevTools` extension for Chrome or Firefox.
|
||||
The extension is available for download from the Chrome Web Store or the Firefox Add-ons website.
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { permissionApiRef } from '@backstage/plugin-permission-react';
|
||||
import {
|
||||
MockPermissionApi,
|
||||
mockApis,
|
||||
renderInTestApp,
|
||||
TestApiProvider,
|
||||
} from '@backstage/test-utils';
|
||||
@@ -46,7 +46,6 @@ describe('EntityPage Test', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const mockPermissionApi = new MockPermissionApi();
|
||||
const rootRouteRef = catalogPlugin.routes.catalogIndex;
|
||||
|
||||
describe('cicdContent', () => {
|
||||
@@ -55,7 +54,7 @@ describe('EntityPage Test', () => {
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[starredEntitiesApiRef, new MockStarredEntitiesApi()],
|
||||
[permissionApiRef, mockPermissionApi],
|
||||
[permissionApiRef, mockApis.permission()],
|
||||
]}
|
||||
>
|
||||
<EntityProvider entity={entity}>
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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.
|
||||
*/
|
||||
|
||||
import {
|
||||
createCredentialsWithNonePrincipal,
|
||||
createCredentialsWithServicePrincipal,
|
||||
createCredentialsWithUserPrincipal,
|
||||
} from './helpers';
|
||||
|
||||
describe('credentials', () => {
|
||||
it('should be created', () => {
|
||||
expect(createCredentialsWithServicePrincipal('my-service')).toEqual({
|
||||
$$type: '@backstage/BackstageCredentials',
|
||||
version: 'v1',
|
||||
principal: {
|
||||
type: 'service',
|
||||
subject: 'my-service',
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
createCredentialsWithUserPrincipal('user:default/mock', 'my-token'),
|
||||
).toEqual({
|
||||
$$type: '@backstage/BackstageCredentials',
|
||||
version: 'v1',
|
||||
principal: {
|
||||
type: 'user',
|
||||
userEntityRef: 'user:default/mock',
|
||||
},
|
||||
});
|
||||
|
||||
expect(createCredentialsWithNonePrincipal()).toEqual({
|
||||
$$type: '@backstage/BackstageCredentials',
|
||||
version: 'v1',
|
||||
principal: {
|
||||
type: 'none',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should not include tokens when serialized', () => {
|
||||
expect(
|
||||
JSON.stringify(
|
||||
createCredentialsWithServicePrincipal('my-service', 'my-token'),
|
||||
),
|
||||
).not.toMatch(/my-token/);
|
||||
|
||||
expect(
|
||||
JSON.stringify(
|
||||
createCredentialsWithUserPrincipal('user:default/mock', 'my-token'),
|
||||
),
|
||||
).not.toMatch(/my-token/);
|
||||
});
|
||||
});
|
||||
@@ -28,16 +28,23 @@ export function createCredentialsWithServicePrincipal(
|
||||
token?: string,
|
||||
accessRestrictions?: BackstagePrincipalAccessRestrictions,
|
||||
): InternalBackstageCredentials<BackstageServicePrincipal> {
|
||||
return {
|
||||
$$type: '@backstage/BackstageCredentials',
|
||||
version: 'v1',
|
||||
token,
|
||||
principal: {
|
||||
type: 'service',
|
||||
subject: sub,
|
||||
accessRestrictions,
|
||||
return Object.defineProperty(
|
||||
{
|
||||
$$type: '@backstage/BackstageCredentials',
|
||||
version: 'v1',
|
||||
principal: {
|
||||
type: 'service',
|
||||
subject: sub,
|
||||
accessRestrictions,
|
||||
},
|
||||
},
|
||||
};
|
||||
'token',
|
||||
{
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
value: token,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function createCredentialsWithUserPrincipal(
|
||||
@@ -45,16 +52,23 @@ export function createCredentialsWithUserPrincipal(
|
||||
token: string,
|
||||
expiresAt?: Date,
|
||||
): InternalBackstageCredentials<BackstageUserPrincipal> {
|
||||
return {
|
||||
$$type: '@backstage/BackstageCredentials',
|
||||
version: 'v1',
|
||||
token,
|
||||
expiresAt,
|
||||
principal: {
|
||||
type: 'user',
|
||||
userEntityRef: sub,
|
||||
return Object.defineProperty(
|
||||
{
|
||||
$$type: '@backstage/BackstageCredentials',
|
||||
version: 'v1',
|
||||
expiresAt,
|
||||
principal: {
|
||||
type: 'user',
|
||||
userEntityRef: sub,
|
||||
},
|
||||
},
|
||||
};
|
||||
'token',
|
||||
{
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
value: token,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function createCredentialsWithNonePrincipal(): InternalBackstageCredentials<BackstageNonePrincipal> {
|
||||
|
||||
+3
@@ -106,6 +106,9 @@ const rootHttpRouterServiceFactoryWithOptions = (
|
||||
lifecycle,
|
||||
healthRouter,
|
||||
applyDefaults() {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
app.set('json spaces', 2);
|
||||
}
|
||||
app.use(middleware.helmet());
|
||||
app.use(middleware.cors());
|
||||
app.use(middleware.compression());
|
||||
|
||||
@@ -150,7 +150,7 @@ export function mockErrorHandler(): ErrorRequestHandler<
|
||||
Record<string, any>
|
||||
>;
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export namespace mockServices {
|
||||
// (undocumented)
|
||||
export function auth(options?: {
|
||||
@@ -491,65 +491,64 @@ export class TestDatabases {
|
||||
// src/next/services/mockCredentials.d.ts:116:9 - (ae-undocumented) Missing documentation for "invalidToken".
|
||||
// src/next/services/mockCredentials.d.ts:117:9 - (ae-undocumented) Missing documentation for "invalidHeader".
|
||||
// src/next/services/mockServices.d.ts:5:1 - (ae-undocumented) Missing documentation for "ServiceMock".
|
||||
// src/next/services/mockServices.d.ts:13:1 - (ae-undocumented) Missing documentation for "mockServices".
|
||||
// src/next/services/mockServices.d.ts:14:5 - (ae-undocumented) Missing documentation for "rootConfig".
|
||||
// src/next/services/mockServices.d.ts:15:5 - (ae-undocumented) Missing documentation for "rootConfig".
|
||||
// src/next/services/mockServices.d.ts:16:9 - (ae-undocumented) Missing documentation for "Options".
|
||||
// src/next/services/mockServices.d.ts:19:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:20:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:22:5 - (ae-undocumented) Missing documentation for "rootLogger".
|
||||
// src/next/services/mockServices.d.ts:23:5 - (ae-undocumented) Missing documentation for "rootLogger".
|
||||
// src/next/services/mockServices.d.ts:24:9 - (ae-undocumented) Missing documentation for "Options".
|
||||
// src/next/services/mockServices.d.ts:27:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:28:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:30:5 - (ae-undocumented) Missing documentation for "auth".
|
||||
// src/next/services/mockServices.d.ts:34:5 - (ae-undocumented) Missing documentation for "auth".
|
||||
// src/next/services/mockServices.d.ts:35:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:36:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:38:5 - (ae-undocumented) Missing documentation for "discovery".
|
||||
// src/next/services/mockServices.d.ts:39:5 - (ae-undocumented) Missing documentation for "discovery".
|
||||
// src/next/services/mockServices.d.ts:40:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:41:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:61:5 - (ae-undocumented) Missing documentation for "httpAuth".
|
||||
// src/next/services/mockServices.d.ts:72:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:82:5 - (ae-undocumented) Missing documentation for "userInfo".
|
||||
// src/next/services/mockServices.d.ts:90:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:92:5 - (ae-undocumented) Missing documentation for "cache".
|
||||
// src/next/services/mockServices.d.ts:93:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:94:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:96:5 - (ae-undocumented) Missing documentation for "database".
|
||||
// src/next/services/mockServices.d.ts:97:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:98:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:100:5 - (ae-undocumented) Missing documentation for "rootHealth".
|
||||
// src/next/services/mockServices.d.ts:101:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:102:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:104:5 - (ae-undocumented) Missing documentation for "httpRouter".
|
||||
// src/next/services/mockServices.d.ts:105:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:106:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:108:5 - (ae-undocumented) Missing documentation for "rootHttpRouter".
|
||||
// src/next/services/mockServices.d.ts:109:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:110:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:112:5 - (ae-undocumented) Missing documentation for "lifecycle".
|
||||
// src/next/services/mockServices.d.ts:113:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:114:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:116:5 - (ae-undocumented) Missing documentation for "logger".
|
||||
// src/next/services/mockServices.d.ts:117:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:118:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:120:5 - (ae-undocumented) Missing documentation for "permissions".
|
||||
// src/next/services/mockServices.d.ts:121:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:122:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:124:5 - (ae-undocumented) Missing documentation for "rootLifecycle".
|
||||
// src/next/services/mockServices.d.ts:125:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:126:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:128:5 - (ae-undocumented) Missing documentation for "scheduler".
|
||||
// src/next/services/mockServices.d.ts:129:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:130:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:132:5 - (ae-undocumented) Missing documentation for "urlReader".
|
||||
// src/next/services/mockServices.d.ts:133:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:134:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:136:5 - (ae-undocumented) Missing documentation for "events".
|
||||
// src/next/services/mockServices.d.ts:137:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:138:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:53:5 - (ae-undocumented) Missing documentation for "rootConfig".
|
||||
// src/next/services/mockServices.d.ts:54:5 - (ae-undocumented) Missing documentation for "rootConfig".
|
||||
// src/next/services/mockServices.d.ts:55:9 - (ae-undocumented) Missing documentation for "Options".
|
||||
// src/next/services/mockServices.d.ts:58:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:59:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:61:5 - (ae-undocumented) Missing documentation for "rootLogger".
|
||||
// src/next/services/mockServices.d.ts:62:5 - (ae-undocumented) Missing documentation for "rootLogger".
|
||||
// src/next/services/mockServices.d.ts:63:9 - (ae-undocumented) Missing documentation for "Options".
|
||||
// src/next/services/mockServices.d.ts:66:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:67:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:69:5 - (ae-undocumented) Missing documentation for "auth".
|
||||
// src/next/services/mockServices.d.ts:73:5 - (ae-undocumented) Missing documentation for "auth".
|
||||
// src/next/services/mockServices.d.ts:74:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:75:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:77:5 - (ae-undocumented) Missing documentation for "discovery".
|
||||
// src/next/services/mockServices.d.ts:78:5 - (ae-undocumented) Missing documentation for "discovery".
|
||||
// src/next/services/mockServices.d.ts:79:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:80:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:100:5 - (ae-undocumented) Missing documentation for "httpAuth".
|
||||
// src/next/services/mockServices.d.ts:111:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:121:5 - (ae-undocumented) Missing documentation for "userInfo".
|
||||
// src/next/services/mockServices.d.ts:129:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:131:5 - (ae-undocumented) Missing documentation for "cache".
|
||||
// src/next/services/mockServices.d.ts:132:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:133:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:135:5 - (ae-undocumented) Missing documentation for "database".
|
||||
// src/next/services/mockServices.d.ts:136:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:137:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:139:5 - (ae-undocumented) Missing documentation for "rootHealth".
|
||||
// src/next/services/mockServices.d.ts:140:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:141:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:143:5 - (ae-undocumented) Missing documentation for "httpRouter".
|
||||
// src/next/services/mockServices.d.ts:144:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:145:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:147:5 - (ae-undocumented) Missing documentation for "rootHttpRouter".
|
||||
// src/next/services/mockServices.d.ts:148:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:149:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:151:5 - (ae-undocumented) Missing documentation for "lifecycle".
|
||||
// src/next/services/mockServices.d.ts:152:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:153:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:155:5 - (ae-undocumented) Missing documentation for "logger".
|
||||
// src/next/services/mockServices.d.ts:156:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:157:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:159:5 - (ae-undocumented) Missing documentation for "permissions".
|
||||
// src/next/services/mockServices.d.ts:160:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:161:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:163:5 - (ae-undocumented) Missing documentation for "rootLifecycle".
|
||||
// src/next/services/mockServices.d.ts:164:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:165:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:167:5 - (ae-undocumented) Missing documentation for "scheduler".
|
||||
// src/next/services/mockServices.d.ts:168:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:169:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:171:5 - (ae-undocumented) Missing documentation for "urlReader".
|
||||
// src/next/services/mockServices.d.ts:172:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:173:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/services/mockServices.d.ts:175:5 - (ae-undocumented) Missing documentation for "events".
|
||||
// src/next/services/mockServices.d.ts:176:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/next/services/mockServices.d.ts:177:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/next/wiring/TestBackend.d.ts:5:1 - (ae-undocumented) Missing documentation for "TestBackendOptions".
|
||||
// src/next/wiring/TestBackend.d.ts:6:5 - (ae-undocumented) Missing documentation for "extensionPoints".
|
||||
// src/next/wiring/TestBackend.d.ts:14:5 - (ae-undocumented) Missing documentation for "features".
|
||||
|
||||
@@ -128,7 +128,46 @@ function simpleMock<TService>(
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock implementations of the core services, to be used in tests.
|
||||
*
|
||||
* @public
|
||||
* @remarks
|
||||
*
|
||||
* There are some variations among the services depending on what needs tests
|
||||
* might have, but overall there are three main usage patterns:
|
||||
*
|
||||
* 1. Creating an actual fake service instance, often with a simplified version
|
||||
* of functionality, by calling the mock service itself as a function.
|
||||
*
|
||||
* ```ts
|
||||
* // The function often accepts parameters that control its behavior
|
||||
* const foo = mockServices.foo();
|
||||
* ```
|
||||
*
|
||||
* 2. Creating a mock service, where all methods are replaced with jest mocks, by
|
||||
* calling the service's `mock` function.
|
||||
*
|
||||
* ```ts
|
||||
* // You can optionally supply a subset of its methods to implement
|
||||
* const foo = mockServices.foo.mock({
|
||||
* someMethod: () => 'mocked result',
|
||||
* });
|
||||
* // After exercising your test, you can make assertions on the mock:
|
||||
* expect(foo.someMethod).toHaveBeenCalledTimes(2);
|
||||
* expect(foo.otherMethod).toHaveBeenCalledWith(testData);
|
||||
* ```
|
||||
*
|
||||
* 3. Creating a service factory that behaves similarly to the mock as per above.
|
||||
*
|
||||
* ```ts
|
||||
* await startTestBackend({
|
||||
* features: [
|
||||
* mockServices.foo.factory({
|
||||
* someMethod: () => 'mocked result',
|
||||
* })
|
||||
* ],
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export namespace mockServices {
|
||||
export function rootConfig(options?: rootConfig.Options): RootConfigService {
|
||||
|
||||
+6
-11
@@ -15,8 +15,8 @@
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { IdentityApi } from '@backstage/core-plugin-api';
|
||||
import { IdentityAuthInjectorFetchMiddleware } from './IdentityAuthInjectorFetchMiddleware';
|
||||
import { mockApis } from '@backstage/test-utils';
|
||||
|
||||
describe('IdentityAuthInjectorFetchMiddleware', () => {
|
||||
it('creates using defaults', async () => {
|
||||
@@ -58,10 +58,7 @@ describe('IdentityAuthInjectorFetchMiddleware', () => {
|
||||
});
|
||||
|
||||
it('injects the header only when a token is available', async () => {
|
||||
const tokenFunction = jest.fn();
|
||||
const identityApi = {
|
||||
getCredentials: tokenFunction,
|
||||
} as unknown as IdentityApi;
|
||||
const identityApi = mockApis.identity.mock();
|
||||
|
||||
const middleware = new IdentityAuthInjectorFetchMiddleware(
|
||||
identityApi,
|
||||
@@ -73,27 +70,25 @@ describe('IdentityAuthInjectorFetchMiddleware', () => {
|
||||
const outer = middleware.apply(inner);
|
||||
|
||||
// No token available
|
||||
tokenFunction.mockResolvedValueOnce({ token: undefined });
|
||||
identityApi.getCredentials.mockResolvedValueOnce({ token: undefined });
|
||||
await outer(new Request('https://example.com'));
|
||||
expect([...inner.mock.calls[0][0].headers.entries()]).toEqual([]);
|
||||
|
||||
// Supply a token, header gets added
|
||||
tokenFunction.mockResolvedValueOnce({ token: 'token' });
|
||||
identityApi.getCredentials.mockResolvedValueOnce({ token: 'token' });
|
||||
await outer(new Request('https://example.com'));
|
||||
expect([...inner.mock.calls[1][0].headers.entries()]).toEqual([
|
||||
['authorization', 'Bearer token'],
|
||||
]);
|
||||
|
||||
// Token no longer available
|
||||
tokenFunction.mockResolvedValueOnce({ token: undefined });
|
||||
identityApi.getCredentials.mockResolvedValueOnce({ token: undefined });
|
||||
await outer(new Request('https://example.com'));
|
||||
expect([...inner.mock.calls[2][0].headers.entries()]).toEqual([]);
|
||||
});
|
||||
|
||||
it('does not overwrite an existing header with the same name', async () => {
|
||||
const identityApi = {
|
||||
getCredentials: () => ({ token: 'token' }),
|
||||
} as unknown as IdentityApi;
|
||||
const identityApi = mockApis.identity({ token: 'token' });
|
||||
|
||||
const middleware = new IdentityAuthInjectorFetchMiddleware(
|
||||
identityApi,
|
||||
|
||||
+10
-12
@@ -14,15 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DiscoveryApi } from '@backstage/core-plugin-api';
|
||||
import { mockApis } from '@backstage/test-utils';
|
||||
import { PluginProtocolResolverFetchMiddleware } from './PluginProtocolResolverFetchMiddleware';
|
||||
|
||||
describe('PluginProtocolResolverFetchMiddleware', () => {
|
||||
it.each([['https://passthrough.com/a']])(
|
||||
'passes through regular URLs, %p',
|
||||
async url => {
|
||||
const resolve = jest.fn();
|
||||
const discoveryApi = { getBaseUrl: resolve } as unknown as DiscoveryApi;
|
||||
const discoveryApi = mockApis.discovery.mock();
|
||||
const middleware = new PluginProtocolResolverFetchMiddleware(
|
||||
discoveryApi,
|
||||
);
|
||||
@@ -31,7 +30,7 @@ describe('PluginProtocolResolverFetchMiddleware', () => {
|
||||
|
||||
await outer(url);
|
||||
expect(inner.mock.calls[0][0]).toBe(url);
|
||||
expect(resolve).not.toHaveBeenCalled();
|
||||
expect(discoveryApi.getBaseUrl).not.toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
|
||||
@@ -76,30 +75,29 @@ describe('PluginProtocolResolverFetchMiddleware', () => {
|
||||
])(
|
||||
'resolves backstage URLs, %p',
|
||||
async (original, host, resolved, result) => {
|
||||
const resolve = jest.fn();
|
||||
const discoveryApi = { getBaseUrl: resolve } as unknown as DiscoveryApi;
|
||||
const discoveryApi = mockApis.discovery.mock({
|
||||
getBaseUrl: async () => resolved,
|
||||
});
|
||||
const middleware = new PluginProtocolResolverFetchMiddleware(
|
||||
discoveryApi,
|
||||
);
|
||||
const inner = jest.fn();
|
||||
const outer = middleware.apply(inner);
|
||||
|
||||
resolve.mockResolvedValueOnce(resolved);
|
||||
await outer(original);
|
||||
expect(inner.mock.calls[0][0]).toBe(result);
|
||||
expect(resolve).toHaveBeenLastCalledWith(host);
|
||||
expect(discoveryApi.getBaseUrl).toHaveBeenLastCalledWith(host);
|
||||
},
|
||||
);
|
||||
|
||||
it('properly supports transferring request bodies too', async () => {
|
||||
const resolve = jest.fn();
|
||||
const discoveryApi = { getBaseUrl: resolve } as unknown as DiscoveryApi;
|
||||
const discoveryApi = mockApis.discovery.mock({
|
||||
getBaseUrl: async () => 'https://elsewhere.com',
|
||||
});
|
||||
const middleware = new PluginProtocolResolverFetchMiddleware(discoveryApi);
|
||||
const inner = jest.fn();
|
||||
const outer = middleware.apply(inner);
|
||||
|
||||
resolve.mockResolvedValue('https://elsewhere.com');
|
||||
|
||||
await outer('plugin://a', {
|
||||
method: 'POST',
|
||||
body: '123',
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi';
|
||||
import { UrlPatternDiscovery } from '../../DiscoveryApi';
|
||||
import BitbucketAuth from './BitbucketAuth';
|
||||
import { MockConfigApi } from '@backstage/test-utils';
|
||||
import { mockApis } from '@backstage/test-utils';
|
||||
|
||||
const getSession = jest.fn();
|
||||
|
||||
@@ -33,7 +33,7 @@ describe('BitbucketAuth', () => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
const configApi = new MockConfigApi({});
|
||||
const configApi = mockApis.config();
|
||||
|
||||
it.each([
|
||||
['team api write_repository', ['team', 'api', 'write_repository']],
|
||||
|
||||
+2
-2
@@ -17,7 +17,7 @@
|
||||
import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi';
|
||||
import { UrlPatternDiscovery } from '../../DiscoveryApi';
|
||||
import BitbucketServerAuth from './BitbucketServerAuth';
|
||||
import { MockConfigApi } from '@backstage/test-utils';
|
||||
import { mockApis } from '@backstage/test-utils';
|
||||
|
||||
const getSession = jest.fn();
|
||||
|
||||
@@ -41,7 +41,7 @@ describe('BitbucketServerAuth', () => {
|
||||
['PROJECT_ADMIN', 'REPO_READ', 'ACCOUNT_WRITE'],
|
||||
],
|
||||
])(`should normalize scopes correctly - %p`, (scope, scopes) => {
|
||||
const configApi = new MockConfigApi({});
|
||||
const configApi = mockApis.config();
|
||||
|
||||
const bitbucketServerAuth = BitbucketServerAuth.create({
|
||||
configApi: configApi,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { UrlPatternDiscovery } from '../../DiscoveryApi';
|
||||
import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi';
|
||||
import GithubAuth from './GithubAuth';
|
||||
import { MockConfigApi } from '@backstage/test-utils';
|
||||
import { mockApis } from '@backstage/test-utils';
|
||||
|
||||
const getSession = jest.fn();
|
||||
|
||||
@@ -33,7 +33,7 @@ describe('GithubAuth', () => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
const configApi = new MockConfigApi({});
|
||||
const configApi = mockApis.config();
|
||||
|
||||
it('should forward access token request to session manager', async () => {
|
||||
const githubAuth = GithubAuth.create({
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi';
|
||||
import { UrlPatternDiscovery } from '../../DiscoveryApi';
|
||||
import GitlabAuth from './GitlabAuth';
|
||||
import { MockConfigApi } from '@backstage/test-utils';
|
||||
import { mockApis } from '@backstage/test-utils';
|
||||
|
||||
const getSession = jest.fn();
|
||||
|
||||
@@ -40,7 +40,7 @@ describe('GitlabAuth', () => {
|
||||
],
|
||||
['read_repository sudo', ['read_repository', 'sudo']],
|
||||
])(`should normalize scopes correctly - %p`, (scope, scopes) => {
|
||||
const configApi = new MockConfigApi({});
|
||||
const configApi = mockApis.config();
|
||||
|
||||
const gitlabAuth = GitlabAuth.create({
|
||||
configApi: configApi,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import GoogleAuth from './GoogleAuth';
|
||||
import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi';
|
||||
import { UrlPatternDiscovery } from '../../DiscoveryApi';
|
||||
import { MockConfigApi } from '@backstage/test-utils';
|
||||
import { mockApis } from '@backstage/test-utils';
|
||||
|
||||
const PREFIX = 'https://www.googleapis.com/auth/';
|
||||
|
||||
@@ -59,7 +59,7 @@ describe('GoogleAuth', () => {
|
||||
[`${PREFIX}profile`, [`${PREFIX}profile`]],
|
||||
[`${PREFIX}openid`, [`${PREFIX}openid`]],
|
||||
])(`should normalize scopes correctly - %p`, (scope, scopes) => {
|
||||
const configApi = new MockConfigApi({});
|
||||
const configApi = mockApis.config();
|
||||
|
||||
const googleAuth = GoogleAuth.create({
|
||||
configApi: configApi,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import OAuth2 from './OAuth2';
|
||||
import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi';
|
||||
import { UrlPatternDiscovery } from '../../DiscoveryApi';
|
||||
import { MockConfigApi } from '@backstage/test-utils';
|
||||
import { mockApis } from '@backstage/test-utils';
|
||||
|
||||
const theFuture = new Date(Date.now() + 3600000);
|
||||
const thePast = new Date(Date.now() - 10);
|
||||
@@ -35,7 +35,7 @@ jest.mock('../../../../lib/AuthSessionManager', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const configApi = new MockConfigApi({});
|
||||
const configApi = mockApis.config();
|
||||
|
||||
describe('OAuth2', () => {
|
||||
it('should get refreshed access token', async () => {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import OktaAuth from './OktaAuth';
|
||||
import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi';
|
||||
import { UrlPatternDiscovery } from '../../DiscoveryApi';
|
||||
import { MockConfigApi } from '@backstage/test-utils';
|
||||
import { mockApis } from '@backstage/test-utils';
|
||||
|
||||
const PREFIX = 'okta.';
|
||||
|
||||
@@ -51,7 +51,7 @@ describe('OktaAuth', () => {
|
||||
[`${PREFIX}profile`, [`${PREFIX}profile`]],
|
||||
[`${PREFIX}openid`, [`${PREFIX}openid`]],
|
||||
])(`should normalize scopes correctly - %p`, (scope, scopes) => {
|
||||
const configApi = new MockConfigApi({});
|
||||
const configApi = mockApis.config();
|
||||
const auth = OktaAuth.create({
|
||||
configApi: configApi,
|
||||
oauthRequestApi: new MockOAuthApi(),
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { LocalStorageFeatureFlags, NoOpAnalyticsApi } from '../apis';
|
||||
import { LocalStorageFeatureFlags } from '../apis';
|
||||
import {
|
||||
MockAnalyticsApi,
|
||||
mockApis,
|
||||
renderWithEffects,
|
||||
withLogCollector,
|
||||
registerMswTestHooks,
|
||||
@@ -59,7 +59,7 @@ describe('Integration Test', () => {
|
||||
|
||||
const noOpAnalyticsApi = createApiFactory(
|
||||
analyticsApiRef,
|
||||
new NoOpAnalyticsApi(),
|
||||
mockApis.analytics(),
|
||||
);
|
||||
const noopErrorApi = createApiFactory(errorApiRef, {
|
||||
error$() {
|
||||
@@ -575,7 +575,7 @@ describe('Integration Test', () => {
|
||||
});
|
||||
|
||||
it('should track route changes via analytics api', async () => {
|
||||
const mockAnalyticsApi = new MockAnalyticsApi();
|
||||
const mockAnalyticsApi = mockApis.analytics();
|
||||
const apis = [createApiFactory(analyticsApiRef, mockAnalyticsApi)];
|
||||
const app = new AppManager({
|
||||
apis,
|
||||
@@ -608,26 +608,27 @@ describe('Integration Test', () => {
|
||||
);
|
||||
|
||||
// Capture initial and subsequent navigation events with expected context.
|
||||
const capturedEvents = mockAnalyticsApi.getEvents();
|
||||
expect(capturedEvents[0]).toMatchObject({
|
||||
expect(mockAnalyticsApi.captureEvent).toHaveBeenCalledTimes(2);
|
||||
expect(mockAnalyticsApi.captureEvent).toHaveBeenNthCalledWith(1, {
|
||||
action: 'navigate',
|
||||
subject: '/',
|
||||
attributes: {},
|
||||
context: {
|
||||
extension: 'App',
|
||||
pluginId: 'blob',
|
||||
routeRef: 'ref-1-2',
|
||||
},
|
||||
});
|
||||
expect(capturedEvents[1]).toMatchObject({
|
||||
expect(mockAnalyticsApi.captureEvent).toHaveBeenNthCalledWith(2, {
|
||||
action: 'navigate',
|
||||
subject: '/foo',
|
||||
attributes: {},
|
||||
context: {
|
||||
extension: 'App',
|
||||
pluginId: 'plugin2',
|
||||
routeRef: 'ref-2',
|
||||
},
|
||||
});
|
||||
expect(capturedEvents).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should throw some error when the route has duplicate params', async () => {
|
||||
@@ -877,9 +878,9 @@ describe('Integration Test', () => {
|
||||
}),
|
||||
}),
|
||||
};
|
||||
const discoveryApiMock = {
|
||||
getBaseUrl: jest.fn().mockResolvedValue('http://localhost:7007/app'),
|
||||
};
|
||||
const discoveryApiMock = mockApis.discovery.mock({
|
||||
getBaseUrl: async () => 'http://localhost:7007/app',
|
||||
});
|
||||
|
||||
const app = new AppManager({
|
||||
icons,
|
||||
|
||||
@@ -18,7 +18,6 @@ import React from 'react';
|
||||
import {
|
||||
AppComponents,
|
||||
configApiRef,
|
||||
IdentityApi,
|
||||
identityApiRef,
|
||||
SignInPageProps,
|
||||
useApi,
|
||||
@@ -30,7 +29,7 @@ import { render, screen } from '@testing-library/react';
|
||||
import { AppRouter } from './AppRouter';
|
||||
import useAsync from 'react-use/esm/useAsync';
|
||||
import { AppContextProvider } from './AppContext';
|
||||
import { TestApiProvider } from '@backstage/test-utils';
|
||||
import { mockApis, TestApiProvider } from '@backstage/test-utils';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
|
||||
function UserRefDisplay() {
|
||||
@@ -78,13 +77,7 @@ describe('AppRouter', () => {
|
||||
const appIdentityProxy = new AppIdentityProxy();
|
||||
|
||||
const SignInPage = (props: SignInPageProps) => {
|
||||
props.onSignInSuccess({
|
||||
getBackstageIdentity: async () => ({
|
||||
type: 'user',
|
||||
userEntityRef: 'user:default/test',
|
||||
ownershipEntityRefs: ['user:default/test'],
|
||||
}),
|
||||
} as IdentityApi);
|
||||
props.onSignInSuccess(mockApis.identity());
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
createRouteRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { collectRouteIds, resolveRouteBindings } from './resolveRouteBindings';
|
||||
import { MockConfigApi } from '@backstage/test-utils';
|
||||
import { mockApis } from '@backstage/test-utils';
|
||||
|
||||
describe('resolveRouteBindings', () => {
|
||||
it('runs happy path', () => {
|
||||
@@ -30,7 +30,7 @@ describe('resolveRouteBindings', () => {
|
||||
({ bind }) => {
|
||||
bind(external, { myRoute: ref });
|
||||
},
|
||||
new MockConfigApi({}),
|
||||
mockApis.config(),
|
||||
[],
|
||||
);
|
||||
|
||||
@@ -45,7 +45,7 @@ describe('resolveRouteBindings', () => {
|
||||
({ bind }) => {
|
||||
bind(external, { someOtherRoute: ref } as any);
|
||||
},
|
||||
new MockConfigApi({}),
|
||||
mockApis.config(),
|
||||
[],
|
||||
),
|
||||
).toThrow('Key someOtherRoute is not an existing external route');
|
||||
@@ -56,8 +56,10 @@ describe('resolveRouteBindings', () => {
|
||||
const myTarget = createRouteRef({ id: 'test' });
|
||||
const result = resolveRouteBindings(
|
||||
() => {},
|
||||
new MockConfigApi({
|
||||
app: { routes: { bindings: { 'test.mySource': 'test.myTarget' } } },
|
||||
mockApis.config({
|
||||
data: {
|
||||
app: { routes: { bindings: { 'test.mySource': 'test.myTarget' } } },
|
||||
},
|
||||
}),
|
||||
[
|
||||
createPlugin({
|
||||
@@ -84,8 +86,10 @@ describe('resolveRouteBindings', () => {
|
||||
({ bind }) => {
|
||||
bind({ mySource }, { mySource: false });
|
||||
},
|
||||
new MockConfigApi({
|
||||
app: { routes: { bindings: { 'test.mySource': 'myTarget' } } },
|
||||
mockApis.config({
|
||||
data: {
|
||||
app: { routes: { bindings: { 'test.mySource': 'myTarget' } } },
|
||||
},
|
||||
}),
|
||||
[
|
||||
createPlugin({
|
||||
@@ -106,8 +110,8 @@ describe('resolveRouteBindings', () => {
|
||||
({ bind }) => {
|
||||
bind({ mySource }, { mySource: myTarget });
|
||||
},
|
||||
new MockConfigApi({
|
||||
app: { routes: { bindings: { 'test.mySource': false } } },
|
||||
mockApis.config({
|
||||
data: { app: { routes: { bindings: { 'test.mySource': false } } } },
|
||||
}),
|
||||
[
|
||||
createPlugin({
|
||||
@@ -128,7 +132,7 @@ describe('resolveRouteBindings', () => {
|
||||
expect(() =>
|
||||
resolveRouteBindings(
|
||||
() => {},
|
||||
new MockConfigApi({ app: { routes: { bindings: 'derp' } } }),
|
||||
mockApis.config({ data: { app: { routes: { bindings: 'derp' } } } }),
|
||||
[],
|
||||
),
|
||||
).toThrow(
|
||||
@@ -138,8 +142,8 @@ describe('resolveRouteBindings', () => {
|
||||
expect(() =>
|
||||
resolveRouteBindings(
|
||||
() => {},
|
||||
new MockConfigApi({
|
||||
app: { routes: { bindings: { 'test.mySource': true } } },
|
||||
mockApis.config({
|
||||
data: { app: { routes: { bindings: { 'test.mySource': true } } } },
|
||||
}),
|
||||
[],
|
||||
),
|
||||
@@ -150,8 +154,10 @@ describe('resolveRouteBindings', () => {
|
||||
expect(() =>
|
||||
resolveRouteBindings(
|
||||
() => {},
|
||||
new MockConfigApi({
|
||||
app: { routes: { bindings: { 'test.mySource': 'test.myTarget' } } },
|
||||
mockApis.config({
|
||||
data: {
|
||||
app: { routes: { bindings: { 'test.mySource': 'test.myTarget' } } },
|
||||
},
|
||||
}),
|
||||
[],
|
||||
),
|
||||
@@ -162,8 +168,10 @@ describe('resolveRouteBindings', () => {
|
||||
expect(() =>
|
||||
resolveRouteBindings(
|
||||
() => {},
|
||||
new MockConfigApi({
|
||||
app: { routes: { bindings: { 'test.mySource': 'test.myTarget' } } },
|
||||
mockApis.config({
|
||||
data: {
|
||||
app: { routes: { bindings: { 'test.mySource': 'test.myTarget' } } },
|
||||
},
|
||||
}),
|
||||
[
|
||||
createPlugin({
|
||||
@@ -198,17 +206,17 @@ describe('resolveRouteBindings', () => {
|
||||
});
|
||||
|
||||
// defaultTarget wins only if no bind or config matches
|
||||
let result = resolveRouteBindings(() => {}, new MockConfigApi({}), [
|
||||
plugin,
|
||||
]);
|
||||
let result = resolveRouteBindings(() => {}, mockApis.config(), [plugin]);
|
||||
|
||||
expect(result.get(source)).toBe(target1);
|
||||
|
||||
// config wins over defaultTarget
|
||||
result = resolveRouteBindings(
|
||||
() => {},
|
||||
new MockConfigApi({
|
||||
app: { routes: { bindings: { 'test.source': 'test.target2' } } },
|
||||
mockApis.config({
|
||||
data: {
|
||||
app: { routes: { bindings: { 'test.source': 'test.target2' } } },
|
||||
},
|
||||
}),
|
||||
[plugin],
|
||||
);
|
||||
@@ -220,7 +228,7 @@ describe('resolveRouteBindings', () => {
|
||||
({ bind }) => {
|
||||
bind(plugin.externalRoutes, { source: plugin.routes.target2 });
|
||||
},
|
||||
new MockConfigApi({}),
|
||||
mockApis.config(),
|
||||
[plugin],
|
||||
);
|
||||
|
||||
@@ -257,17 +265,15 @@ describe('collectRouteIds', () => {
|
||||
});
|
||||
|
||||
// resolves normally with no config
|
||||
let result = resolveRouteBindings(() => {}, new MockConfigApi({}), [
|
||||
plugin,
|
||||
]);
|
||||
let result = resolveRouteBindings(() => {}, mockApis.config(), [plugin]);
|
||||
|
||||
expect(result.get(source)).toBe(target1);
|
||||
|
||||
// can be disabled
|
||||
result = resolveRouteBindings(
|
||||
() => {},
|
||||
new MockConfigApi({
|
||||
app: { routes: { bindings: { 'test.source': false } } },
|
||||
mockApis.config({
|
||||
data: { app: { routes: { bindings: { 'test.source': false } } } },
|
||||
}),
|
||||
[plugin],
|
||||
);
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { TestApiProvider } from '@backstage/test-utils';
|
||||
|
||||
import { TestApiProvider, mockApis } from '@backstage/test-utils';
|
||||
import React from 'react';
|
||||
import { BackstageRouteObject } from './types';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { RouteTracker } from './RouteTracker';
|
||||
import { Link, MemoryRouter, Route, Routes } from 'react-router-dom';
|
||||
import {
|
||||
AnalyticsApi,
|
||||
analyticsApiRef,
|
||||
createPlugin,
|
||||
createRouteRef,
|
||||
@@ -68,9 +68,7 @@ describe('RouteTracker', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const mockedAnalytics: jest.Mocked<AnalyticsApi> = {
|
||||
captureEvent: jest.fn(),
|
||||
};
|
||||
const mockedAnalytics = mockApis.analytics();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
@@ -13,23 +13,23 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createMocks } from 'react-idle-timer';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { MessageChannel } from 'worker_threads';
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
import { identityApiRef } from '@backstage/core-plugin-api';
|
||||
import { TestApiRegistry, renderInTestApp } from '@backstage/test-utils';
|
||||
import {
|
||||
TestApiRegistry,
|
||||
renderInTestApp,
|
||||
mockApis,
|
||||
} from '@backstage/test-utils';
|
||||
import React from 'react';
|
||||
|
||||
import { AutoLogout } from './AutoLogout';
|
||||
import { cleanup } from '@testing-library/react';
|
||||
|
||||
// Mock the signOut function of identityApiRef
|
||||
const mockSignOut = jest.fn();
|
||||
const mockIdentityApi = {
|
||||
signOut: mockSignOut,
|
||||
getCredentials: jest.fn().mockReturnValue({ token: 'xxx' }),
|
||||
};
|
||||
const mockIdentityApi = mockApis.identity({ token: 'xxx' });
|
||||
const apis = TestApiRegistry.from([identityApiRef, mockIdentityApi]);
|
||||
|
||||
describe('AutoLogout', () => {
|
||||
|
||||
+8
-14
@@ -16,24 +16,18 @@
|
||||
|
||||
import React from 'react';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import { TestApiRegistry, renderInTestApp } from '@backstage/test-utils';
|
||||
import {
|
||||
TestApiRegistry,
|
||||
renderInTestApp,
|
||||
mockApis,
|
||||
} from '@backstage/test-utils';
|
||||
import { DismissableBanner } from './DismissableBanner';
|
||||
import { ApiProvider, WebStorage } from '@backstage/core-app-api';
|
||||
import { storageApiRef, StorageApi } from '@backstage/core-plugin-api';
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
import { storageApiRef } from '@backstage/core-plugin-api';
|
||||
import { screen } from '@testing-library/react';
|
||||
|
||||
describe('<DismissableBanner />', () => {
|
||||
let apis: TestApiRegistry;
|
||||
const mockErrorApi = { post: jest.fn(), error$: jest.fn() };
|
||||
const createWebStorage = (): StorageApi => {
|
||||
return WebStorage.create({
|
||||
errorApi: mockErrorApi,
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
apis = TestApiRegistry.from([storageApiRef, createWebStorage()]);
|
||||
});
|
||||
const apis = TestApiRegistry.from([storageApiRef, mockApis.storage()]);
|
||||
|
||||
it('renders the message and the popover', async () => {
|
||||
await renderInTestApp(
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import React, { ComponentType } from 'react';
|
||||
import { fireEvent, waitFor, screen, renderHook } from '@testing-library/react';
|
||||
import {
|
||||
MockAnalyticsApi,
|
||||
mockApis,
|
||||
TestApiProvider,
|
||||
renderInTestApp,
|
||||
} from '@backstage/test-utils';
|
||||
@@ -71,7 +71,7 @@ describe('<Link />', () => {
|
||||
|
||||
it('captures click using analytics api', async () => {
|
||||
const linkText = 'Navigate!';
|
||||
const analyticsApi = new MockAnalyticsApi();
|
||||
const analyticsApi = mockApis.analytics();
|
||||
const customOnClick = jest.fn();
|
||||
|
||||
await renderInTestApp(
|
||||
@@ -86,13 +86,15 @@ describe('<Link />', () => {
|
||||
|
||||
// Analytics event should have been fired.
|
||||
await waitFor(() => {
|
||||
expect(analyticsApi.getEvents()[0]).toMatchObject({
|
||||
action: 'click',
|
||||
subject: linkText,
|
||||
attributes: {
|
||||
to: '/test',
|
||||
},
|
||||
});
|
||||
expect(analyticsApi.captureEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: 'click',
|
||||
subject: linkText,
|
||||
attributes: {
|
||||
to: '/test',
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
// Custom onClick handler should have still been fired too.
|
||||
expect(customOnClick).toHaveBeenCalled();
|
||||
@@ -101,7 +103,7 @@ describe('<Link />', () => {
|
||||
|
||||
it('does not capture click when noTrack is set', async () => {
|
||||
const linkText = 'Navigate!';
|
||||
const analyticsApi = new MockAnalyticsApi();
|
||||
const analyticsApi = mockApis.analytics();
|
||||
const customOnClick = jest.fn();
|
||||
|
||||
await renderInTestApp(
|
||||
@@ -120,7 +122,7 @@ describe('<Link />', () => {
|
||||
expect(customOnClick).toHaveBeenCalled();
|
||||
|
||||
// But there should be no analytics event.
|
||||
expect(analyticsApi.getEvents()).toHaveLength(0);
|
||||
expect(analyticsApi.captureEvent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import { configApiRef } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
MockConfigApi,
|
||||
mockApis,
|
||||
renderInTestApp,
|
||||
TestApiProvider,
|
||||
} from '@backstage/test-utils';
|
||||
@@ -24,17 +24,19 @@ import { act, fireEvent, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { SupportButton } from './SupportButton';
|
||||
|
||||
const configApi = new MockConfigApi({
|
||||
app: {
|
||||
support: {
|
||||
url: 'https://github.com',
|
||||
items: [
|
||||
{
|
||||
title: 'Github',
|
||||
icon: 'github',
|
||||
links: [{ title: 'Github Issues', url: '/issues' }],
|
||||
},
|
||||
],
|
||||
const configApi = mockApis.config({
|
||||
data: {
|
||||
app: {
|
||||
support: {
|
||||
url: 'https://github.com',
|
||||
items: [
|
||||
{
|
||||
title: 'Github',
|
||||
icon: 'github',
|
||||
links: [{ title: 'Github Issues', url: '/issues' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@ import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import {
|
||||
TestApiProvider,
|
||||
mockApis,
|
||||
registerMswTestHooks,
|
||||
wrapInTestApp,
|
||||
} from '@backstage/test-utils';
|
||||
@@ -37,9 +38,7 @@ describe('ProxiedSignInPage', () => {
|
||||
apis={[
|
||||
[
|
||||
discoveryApiRef,
|
||||
{
|
||||
getBaseUrl: async () => 'http://example.com/api/auth',
|
||||
},
|
||||
mockApis.discovery({ baseUrl: 'http://example.com' }),
|
||||
],
|
||||
]}
|
||||
>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
MockAnalyticsApi,
|
||||
mockApis,
|
||||
TestApiProvider,
|
||||
renderInTestApp,
|
||||
} from '@backstage/test-utils';
|
||||
@@ -36,9 +36,8 @@ const useStyles = makeStyles({
|
||||
},
|
||||
});
|
||||
|
||||
let analyticsApiMock: MockAnalyticsApi;
|
||||
|
||||
const handleSidebarItemClick = jest.fn();
|
||||
const analyticsApiMock = mockApis.analytics();
|
||||
|
||||
async function renderSidebar() {
|
||||
const { result } = renderHook(() => useStyles());
|
||||
@@ -79,7 +78,6 @@ async function renderSidebar() {
|
||||
describe('Items', () => {
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
analyticsApiMock = new MockAnalyticsApi();
|
||||
await renderSidebar();
|
||||
});
|
||||
|
||||
@@ -107,8 +105,7 @@ describe('Items', () => {
|
||||
await screen.findByRole('button', { name: /create/i }),
|
||||
);
|
||||
expect(handleSidebarItemClick).toHaveBeenCalledTimes(1);
|
||||
expect(analyticsApiMock.getEvents()).toHaveLength(1);
|
||||
expect(analyticsApiMock.getEvents()[0]).toMatchObject({
|
||||
expect(analyticsApiMock.captureEvent).toHaveBeenCalledWith({
|
||||
action: 'click',
|
||||
subject: 'Create...',
|
||||
context: { routeRef: 'unknown', pluginId: 'root', extension: 'App' },
|
||||
@@ -119,8 +116,7 @@ describe('Items', () => {
|
||||
it('should send link clicks to analytics', async () => {
|
||||
await userEvent.click(await screen.findByRole('link', { name: /docs/i }));
|
||||
expect(handleSidebarItemClick).toHaveBeenCalledTimes(1);
|
||||
expect(analyticsApiMock.getEvents()).toHaveLength(1);
|
||||
expect(analyticsApiMock.getEvents()[0]).toMatchObject({
|
||||
expect(analyticsApiMock.captureEvent).toHaveBeenCalledWith({
|
||||
action: 'click',
|
||||
subject: 'Docs',
|
||||
context: { routeRef: 'unknown', pluginId: 'root', extension: 'App' },
|
||||
@@ -132,10 +128,10 @@ describe('Items', () => {
|
||||
await userEvent.click(
|
||||
await screen.findByRole('link', { name: /explore/i }),
|
||||
);
|
||||
expect(handleSidebarItemClick).toHaveBeenCalledTimes(1);
|
||||
expect(analyticsApiMock.getEvents()).toHaveLength(0);
|
||||
expect(analyticsApiMock.captureEvent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('SidebarSearchField', () => {
|
||||
it('should be defaultPrevented when enter is pressed', async () => {
|
||||
const searchEvent = createEvent.keyDown(
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { TestApiProvider } from '@backstage/test-utils';
|
||||
import React, { useEffect } from 'react';
|
||||
import { BackstageRouteObject } from './types';
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
createFrontendPlugin,
|
||||
createRouteRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { MockConfigApi, TestApiRegistry } from '@backstage/test-utils';
|
||||
import { mockApis, TestApiRegistry } from '@backstage/test-utils';
|
||||
import appPlugin from '@backstage/plugin-app';
|
||||
|
||||
import { readAppExtensionsConfig } from '../tree/readAppExtensionsConfig';
|
||||
@@ -92,7 +92,7 @@ function routeInfoFromExtensions(extensions: ExtensionDefinition[]) {
|
||||
builtinExtensions: [
|
||||
resolveExtensionDefinition(Root, { namespace: 'root' }),
|
||||
],
|
||||
parameters: readAppExtensionsConfig(new MockConfigApi({})),
|
||||
parameters: readAppExtensionsConfig(mockApis.config()),
|
||||
forbidden: new Set(['root']),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { screen, render } from '@testing-library/react';
|
||||
import { createSpecializedApp } from './createSpecializedApp';
|
||||
import { MockConfigApi } from '@backstage/test-utils';
|
||||
import { mockApis } from '@backstage/test-utils';
|
||||
import React from 'react';
|
||||
import {
|
||||
configApiRef,
|
||||
@@ -98,7 +98,7 @@ describe('createSpecializedApp', () => {
|
||||
|
||||
it('should forward config', () => {
|
||||
const app = createSpecializedApp({
|
||||
config: new MockConfigApi({ test: 'foo' }),
|
||||
config: mockApis.config({ data: { test: 'foo' } }),
|
||||
features: [
|
||||
createFrontendPlugin({
|
||||
id: 'test',
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import { CreateAppFeatureLoader, createApp } from './createApp';
|
||||
import { MockConfigApi, renderWithEffects } from '@backstage/test-utils';
|
||||
import { mockApis, renderWithEffects } from '@backstage/test-utils';
|
||||
import React from 'react';
|
||||
import { featureFlagsApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import appPlugin from '@backstage/plugin-app';
|
||||
@@ -35,12 +35,14 @@ describe('createApp', () => {
|
||||
it('should allow themes to be installed', async () => {
|
||||
const app = createApp({
|
||||
configLoader: async () => ({
|
||||
config: new MockConfigApi({
|
||||
app: {
|
||||
extensions: [
|
||||
{ 'theme:app/light': false },
|
||||
{ 'theme:app/dark': false },
|
||||
],
|
||||
config: mockApis.config({
|
||||
data: {
|
||||
app: {
|
||||
extensions: [
|
||||
{ 'theme:app/light': false },
|
||||
{ 'theme:app/dark': false },
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
@@ -72,7 +74,7 @@ describe('createApp', () => {
|
||||
it('should deduplicate features keeping the last received one', async () => {
|
||||
const duplicatedFeatureId = 'test';
|
||||
const app = createApp({
|
||||
configLoader: async () => ({ config: new MockConfigApi({}) }),
|
||||
configLoader: async () => ({ config: mockApis.config() }),
|
||||
features: [
|
||||
createFrontendPlugin({
|
||||
id: duplicatedFeatureId,
|
||||
@@ -135,7 +137,7 @@ describe('createApp', () => {
|
||||
|
||||
const app = createApp({
|
||||
configLoader: async () => ({
|
||||
config: new MockConfigApi({ key: 'config-value' }),
|
||||
config: mockApis.config({ data: { key: 'config-value' } }),
|
||||
}),
|
||||
features: [appPlugin, loader],
|
||||
});
|
||||
@@ -159,7 +161,7 @@ describe('createApp', () => {
|
||||
|
||||
const app = createApp({
|
||||
configLoader: async () => ({
|
||||
config: new MockConfigApi({}),
|
||||
config: mockApis.config(),
|
||||
}),
|
||||
features: [loader],
|
||||
});
|
||||
@@ -173,7 +175,7 @@ describe('createApp', () => {
|
||||
|
||||
it('should register feature flags', async () => {
|
||||
const app = createApp({
|
||||
configLoader: async () => ({ config: new MockConfigApi({}) }),
|
||||
configLoader: async () => ({ config: mockApis.config() }),
|
||||
features: [
|
||||
appPlugin.withOverrides({
|
||||
extensions: [
|
||||
@@ -227,7 +229,7 @@ describe('createApp', () => {
|
||||
let appTreeApi: AppTreeApi | undefined = undefined;
|
||||
|
||||
const app = createApp({
|
||||
configLoader: async () => ({ config: new MockConfigApi({}) }),
|
||||
configLoader: async () => ({ config: mockApis.config() }),
|
||||
features: [
|
||||
createFrontendPlugin({
|
||||
id: 'my-plugin',
|
||||
|
||||
@@ -15,14 +15,13 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
IdentityApi,
|
||||
SignInPageBlueprint,
|
||||
createFrontendModule,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { createPublicSignInApp } from './createPublicSignInApp';
|
||||
import { MockConfigApi } from '@backstage/test-utils';
|
||||
import { mockApis } from '@backstage/test-utils';
|
||||
|
||||
describe('createPublicSignInApp', () => {
|
||||
beforeEach(() => {
|
||||
@@ -31,7 +30,7 @@ describe('createPublicSignInApp', () => {
|
||||
|
||||
it('should render a sign-in page', async () => {
|
||||
const app = createPublicSignInApp({
|
||||
configLoader: async () => ({ config: new MockConfigApi({}) }),
|
||||
configLoader: async () => ({ config: mockApis.config() }),
|
||||
features: [
|
||||
createFrontendModule({
|
||||
pluginId: 'app',
|
||||
@@ -59,7 +58,7 @@ describe('createPublicSignInApp', () => {
|
||||
.mockReturnValue();
|
||||
|
||||
const app = createPublicSignInApp({
|
||||
configLoader: async () => ({ config: new MockConfigApi({}) }),
|
||||
configLoader: async () => ({ config: mockApis.config() }),
|
||||
features: [
|
||||
createFrontendModule({
|
||||
pluginId: 'app',
|
||||
@@ -70,9 +69,9 @@ describe('createPublicSignInApp', () => {
|
||||
async () =>
|
||||
({ onSignInSuccess }) => {
|
||||
useEffect(() => {
|
||||
onSignInSuccess({
|
||||
getCredentials: async () => ({ token: 'mock-token' }),
|
||||
} as IdentityApi);
|
||||
onSignInSuccess(
|
||||
mockApis.identity({ token: 'mock-token' }),
|
||||
);
|
||||
}, [onSignInSuccess]);
|
||||
return <div />;
|
||||
},
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { act, screen, waitFor } from '@testing-library/react';
|
||||
import {
|
||||
MockAnalyticsApi,
|
||||
mockApis,
|
||||
TestApiProvider,
|
||||
withLogCollector,
|
||||
} from '@backstage/test-utils';
|
||||
@@ -93,7 +93,7 @@ describe('ExtensionBoundary', () => {
|
||||
it('should wrap children with analytics context', async () => {
|
||||
const action = 'render';
|
||||
const subject = 'analytics';
|
||||
const analyticsApiMock = new MockAnalyticsApi();
|
||||
const analyticsApiMock = mockApis.analytics();
|
||||
|
||||
const AnalyticsComponent = () => {
|
||||
const analytics = useAnalytics();
|
||||
@@ -112,17 +112,15 @@ describe('ExtensionBoundary', () => {
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const event = analyticsApiMock
|
||||
.getEvents()
|
||||
.find(e => e.subject === subject);
|
||||
|
||||
expect(event).toMatchObject({
|
||||
action,
|
||||
subject,
|
||||
context: {
|
||||
extensionId: 'test',
|
||||
},
|
||||
});
|
||||
expect(analyticsApiMock.captureEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action,
|
||||
subject,
|
||||
context: expect.objectContaining({
|
||||
extensionId: 'test',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -136,7 +134,7 @@ describe('ExtensionBoundary', () => {
|
||||
});
|
||||
return null;
|
||||
};
|
||||
const analyticsApiMock = new MockAnalyticsApi();
|
||||
const analyticsApiMock = mockApis.analytics();
|
||||
|
||||
await act(async () => {
|
||||
renderInTestApp(
|
||||
@@ -147,7 +145,7 @@ describe('ExtensionBoundary', () => {
|
||||
);
|
||||
});
|
||||
|
||||
expect(analyticsApiMock.getEvents()).toEqual([
|
||||
expect(analyticsApiMock.captureEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: 'navigate',
|
||||
subject: '/',
|
||||
@@ -156,7 +154,9 @@ describe('ExtensionBoundary', () => {
|
||||
extensionId: 'test',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(analyticsApiMock.captureEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ action: 'dummy' }),
|
||||
]);
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,7 +23,7 @@ import { JsonObject } from '@backstage/types';
|
||||
import { createExtension } from './createExtension';
|
||||
import { createExtensionDataRef } from './createExtensionDataRef';
|
||||
import { coreExtensionData } from './coreExtensionData';
|
||||
import { MockConfigApi, renderWithEffects } from '@backstage/test-utils';
|
||||
import { mockApis, renderWithEffects } from '@backstage/test-utils';
|
||||
import { createExtensionInput } from './createExtensionInput';
|
||||
|
||||
const nameExtensionDataRef = createExtensionDataRef<string>().with({
|
||||
@@ -128,7 +128,7 @@ function createTestAppRoot({
|
||||
}) {
|
||||
return createApp({
|
||||
features: [...features],
|
||||
configLoader: async () => ({ config: new MockConfigApi(config) }),
|
||||
configLoader: async () => ({ config: mockApis.config({ data: config }) }),
|
||||
}).createRoot();
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,6 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@testing-library/react": "^16.0.0",
|
||||
"@types/jest": "*",
|
||||
"@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
||||
"react": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
||||
|
||||
@@ -3,13 +3,12 @@
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
/// <reference types="jest" />
|
||||
/// <reference types="react" />
|
||||
|
||||
import { AnalyticsApi } from '@backstage/frontend-plugin-api';
|
||||
import { AnalyticsEvent } from '@backstage/frontend-plugin-api';
|
||||
import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api';
|
||||
import { ApiFactory } from '@backstage/frontend-plugin-api';
|
||||
import { ApiMock } from '@backstage/test-utils';
|
||||
import { AppNode } from '@backstage/frontend-plugin-api';
|
||||
import { AppNodeInstance } from '@backstage/frontend-plugin-api';
|
||||
import { ErrorWithContext } from '@backstage/test-utils';
|
||||
@@ -18,6 +17,7 @@ import { ExtensionDefinition } from '@backstage/frontend-plugin-api';
|
||||
import { ExtensionDefinitionParameters } from '@backstage/frontend-plugin-api';
|
||||
import { FrontendFeature } from '@backstage/frontend-app-api';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { mockApis } from '@backstage/test-utils';
|
||||
import { MockConfigApi } from '@backstage/test-utils';
|
||||
import { MockErrorApi } from '@backstage/test-utils';
|
||||
import { MockErrorApiOptions } from '@backstage/test-utils';
|
||||
@@ -34,14 +34,7 @@ import { TestApiProviderProps } from '@backstage/test-utils';
|
||||
import { TestApiRegistry } from '@backstage/test-utils';
|
||||
import { withLogCollector } from '@backstage/test-utils';
|
||||
|
||||
// @public
|
||||
export type ApiMock<TApi> = {
|
||||
factory: ApiFactory<TApi, TApi, {}>;
|
||||
} & {
|
||||
[Key in keyof TApi]: TApi[Key] extends (...args: infer Args) => infer Return
|
||||
? TApi[Key] & jest.MockInstance<Return, Args>
|
||||
: TApi[Key];
|
||||
};
|
||||
export { ApiMock };
|
||||
|
||||
// @public (undocumented)
|
||||
export function createExtensionTester<T extends ExtensionDefinitionParameters>(
|
||||
@@ -103,6 +96,8 @@ export class MockAnalyticsApi implements AnalyticsApi {
|
||||
getEvents(): AnalyticsEvent[];
|
||||
}
|
||||
|
||||
export { mockApis };
|
||||
|
||||
export { MockConfigApi };
|
||||
|
||||
export { MockErrorApi };
|
||||
|
||||
@@ -24,7 +24,8 @@ export {
|
||||
MockPermissionApi,
|
||||
MockStorageApi,
|
||||
type MockStorageBucket,
|
||||
mockApis,
|
||||
type ApiMock,
|
||||
} from '@backstage/test-utils';
|
||||
|
||||
export { type ApiMock } from './ApiMock';
|
||||
export { MockAnalyticsApi } from './AnalyticsApi/MockAnalyticsApi';
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@testing-library/jest-dom": "^6.0.0",
|
||||
"@types/jest": "*",
|
||||
"@types/react": "^18.0.0",
|
||||
"msw": "^1.0.0",
|
||||
"react": "^18.0.2",
|
||||
@@ -73,12 +74,16 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@testing-library/react": "^16.0.0",
|
||||
"@types/jest": "*",
|
||||
"@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
||||
"react": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
||||
"react-router-dom": "6.0.0-beta.0 || ^6.3.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/jest": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { TranslationApi } from '@backstage/core-plugin-api/alpha';
|
||||
import { TranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { TranslationSnapshot } from '@backstage/core-plugin-api/alpha';
|
||||
|
||||
// @alpha (undocumented)
|
||||
// @alpha @deprecated (undocumented)
|
||||
export class MockTranslationApi implements TranslationApi {
|
||||
// (undocumented)
|
||||
static create(): MockTranslationApi;
|
||||
@@ -30,10 +30,10 @@ export class MockTranslationApi implements TranslationApi {
|
||||
|
||||
// Warnings were encountered during analysis:
|
||||
//
|
||||
// src/testUtils/apis/TranslationApi/MockTranslationApi.d.ts:4:1 - (ae-undocumented) Missing documentation for "MockTranslationApi".
|
||||
// src/testUtils/apis/TranslationApi/MockTranslationApi.d.ts:6:5 - (ae-undocumented) Missing documentation for "create".
|
||||
// src/testUtils/apis/TranslationApi/MockTranslationApi.d.ts:8:5 - (ae-undocumented) Missing documentation for "getTranslation".
|
||||
// src/testUtils/apis/TranslationApi/MockTranslationApi.d.ts:11:5 - (ae-undocumented) Missing documentation for "translation$".
|
||||
// src/testUtils/apis/TranslationApi/MockTranslationApi.d.ts:7:1 - (ae-undocumented) Missing documentation for "MockTranslationApi".
|
||||
// src/testUtils/apis/TranslationApi/MockTranslationApi.d.ts:9:5 - (ae-undocumented) Missing documentation for "create".
|
||||
// src/testUtils/apis/TranslationApi/MockTranslationApi.d.ts:11:5 - (ae-undocumented) Missing documentation for "getTranslation".
|
||||
// src/testUtils/apis/TranslationApi/MockTranslationApi.d.ts:14:5 - (ae-undocumented) Missing documentation for "translation$".
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
/// <reference types="jest" />
|
||||
|
||||
import { AnalyticsApi } from '@backstage/core-plugin-api';
|
||||
import { AnalyticsEvent } from '@backstage/core-plugin-api';
|
||||
import { ApiFactory } from '@backstage/core-plugin-api';
|
||||
import { ApiHolder } from '@backstage/core-plugin-api';
|
||||
import { ApiRef } from '@backstage/core-plugin-api';
|
||||
import { AppComponents } from '@backstage/core-plugin-api';
|
||||
@@ -38,6 +41,16 @@ import { RenderResult } from '@testing-library/react';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
import { StorageApi } from '@backstage/core-plugin-api';
|
||||
import { StorageValueSnapshot } from '@backstage/core-plugin-api';
|
||||
import { TranslationApi } from '@backstage/core-plugin-api/alpha';
|
||||
|
||||
// @public
|
||||
export type ApiMock<TApi> = {
|
||||
factory: ApiFactory<TApi, TApi, {}>;
|
||||
} & {
|
||||
[Key in keyof TApi]: TApi[Key] extends (...args: infer Args) => infer Return
|
||||
? TApi[Key] & jest.MockInstance<Return, Args>
|
||||
: TApi[Key];
|
||||
};
|
||||
|
||||
// @public
|
||||
export type AsyncLogCollector = () => Promise<void>;
|
||||
@@ -69,7 +82,7 @@ export type LogCollector = AsyncLogCollector | SyncLogCollector;
|
||||
// @public
|
||||
export type LogFuncs = 'log' | 'warn' | 'error';
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export class MockAnalyticsApi implements AnalyticsApi {
|
||||
// (undocumented)
|
||||
captureEvent(event: AnalyticsEvent): void;
|
||||
@@ -77,10 +90,128 @@ export class MockAnalyticsApi implements AnalyticsApi {
|
||||
getEvents(): AnalyticsEvent[];
|
||||
}
|
||||
|
||||
// @public
|
||||
export namespace mockApis {
|
||||
export function analytics(): AnalyticsApi;
|
||||
export namespace analytics {
|
||||
const // (undocumented)
|
||||
factory: () => ApiFactory<AnalyticsApi, AnalyticsApi, {}>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<AnalyticsApi> | undefined,
|
||||
) => ApiMock<AnalyticsApi>;
|
||||
}
|
||||
export function config(options?: { data?: JsonObject }): ConfigApi;
|
||||
export namespace config {
|
||||
const factory: (
|
||||
options?:
|
||||
| {
|
||||
data?: JsonObject | undefined;
|
||||
}
|
||||
| undefined,
|
||||
) => ApiFactory<Config, Config, {}>;
|
||||
const mock: (partialImpl?: Partial<Config> | undefined) => ApiMock<Config>;
|
||||
}
|
||||
export function discovery(options?: { baseUrl?: string }): DiscoveryApi;
|
||||
export namespace discovery {
|
||||
const // (undocumented)
|
||||
factory: (
|
||||
options?:
|
||||
| {
|
||||
baseUrl?: string | undefined;
|
||||
}
|
||||
| undefined,
|
||||
) => ApiFactory<DiscoveryApi, DiscoveryApi, {}>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<DiscoveryApi> | undefined,
|
||||
) => ApiMock<DiscoveryApi>;
|
||||
}
|
||||
export function identity(options?: {
|
||||
userEntityRef?: string;
|
||||
ownershipEntityRefs?: string[];
|
||||
token?: string;
|
||||
email?: string;
|
||||
displayName?: string;
|
||||
picture?: string;
|
||||
}): IdentityApi;
|
||||
export namespace identity {
|
||||
const // (undocumented)
|
||||
factory: (
|
||||
options?:
|
||||
| {
|
||||
userEntityRef?: string | undefined;
|
||||
ownershipEntityRefs?: string[] | undefined;
|
||||
token?: string | undefined;
|
||||
email?: string | undefined;
|
||||
displayName?: string | undefined;
|
||||
picture?: string | undefined;
|
||||
}
|
||||
| undefined,
|
||||
) => ApiFactory<IdentityApi, IdentityApi, {}>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<IdentityApi> | undefined,
|
||||
) => ApiMock<IdentityApi>;
|
||||
}
|
||||
export function permission(options?: {
|
||||
authorize?:
|
||||
| AuthorizeResult.ALLOW
|
||||
| AuthorizeResult.DENY
|
||||
| ((
|
||||
request: EvaluatePermissionRequest,
|
||||
) => AuthorizeResult.ALLOW | AuthorizeResult.DENY);
|
||||
}): PermissionApi;
|
||||
export namespace permission {
|
||||
const // (undocumented)
|
||||
factory: (
|
||||
options?:
|
||||
| {
|
||||
authorize?:
|
||||
| AuthorizeResult.DENY
|
||||
| AuthorizeResult.ALLOW
|
||||
| ((
|
||||
request: EvaluatePermissionRequest,
|
||||
) => AuthorizeResult.DENY | AuthorizeResult.ALLOW)
|
||||
| undefined;
|
||||
}
|
||||
| undefined,
|
||||
) => ApiFactory<PermissionApi, PermissionApi, {}>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<PermissionApi> | undefined,
|
||||
) => ApiMock<PermissionApi>;
|
||||
}
|
||||
export function storage(options?: { data?: JsonObject }): StorageApi;
|
||||
export namespace storage {
|
||||
const // (undocumented)
|
||||
factory: (
|
||||
options?:
|
||||
| {
|
||||
data?: JsonObject | undefined;
|
||||
}
|
||||
| undefined,
|
||||
) => ApiFactory<StorageApi, StorageApi, {}>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<StorageApi> | undefined,
|
||||
) => ApiMock<StorageApi>;
|
||||
}
|
||||
export function translation(): TranslationApi;
|
||||
export namespace translation {
|
||||
const // (undocumented)
|
||||
factory: () => ApiFactory<TranslationApi, TranslationApi, {}>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<TranslationApi> | undefined,
|
||||
) => ApiMock<TranslationApi>;
|
||||
}
|
||||
}
|
||||
|
||||
// @public @deprecated
|
||||
export function mockBreakpoint(options: { matches: boolean }): void;
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export class MockConfigApi implements ConfigApi {
|
||||
constructor(data: JsonObject);
|
||||
get<T = JsonValue>(key?: string): T;
|
||||
@@ -146,7 +277,7 @@ export interface MockFetchApiOptions {
|
||||
};
|
||||
}
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export class MockPermissionApi implements PermissionApi {
|
||||
constructor(
|
||||
requestHandler?: (
|
||||
@@ -159,7 +290,7 @@ export class MockPermissionApi implements PermissionApi {
|
||||
): Promise<EvaluatePermissionResponse>;
|
||||
}
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export class MockStorageApi implements StorageApi {
|
||||
// (undocumented)
|
||||
static create(data?: MockStorageBucket): MockStorageApi;
|
||||
@@ -177,7 +308,7 @@ export class MockStorageApi implements StorageApi {
|
||||
snapshot<T extends JsonValue>(key: string): StorageValueSnapshot<T>;
|
||||
}
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export type MockStorageBucket = {
|
||||
[key: string]: any;
|
||||
};
|
||||
@@ -276,17 +407,29 @@ export function wrapInTestApp(
|
||||
// Warnings were encountered during analysis:
|
||||
//
|
||||
// src/deprecated.d.ts:5:1 - (ae-undocumented) Missing documentation for "setupRequestMockHandlers".
|
||||
// src/testUtils/apis/AnalyticsApi/MockAnalyticsApi.d.ts:10:5 - (ae-undocumented) Missing documentation for "captureEvent".
|
||||
// src/testUtils/apis/AnalyticsApi/MockAnalyticsApi.d.ts:11:5 - (ae-undocumented) Missing documentation for "getEvents".
|
||||
// src/testUtils/apis/AnalyticsApi/MockAnalyticsApi.d.ts:11:5 - (ae-undocumented) Missing documentation for "captureEvent".
|
||||
// src/testUtils/apis/AnalyticsApi/MockAnalyticsApi.d.ts:12:5 - (ae-undocumented) Missing documentation for "getEvents".
|
||||
// src/testUtils/apis/ErrorApi/MockErrorApi.d.ts:28:5 - (ae-undocumented) Missing documentation for "post".
|
||||
// src/testUtils/apis/ErrorApi/MockErrorApi.d.ts:29:5 - (ae-undocumented) Missing documentation for "error$".
|
||||
// src/testUtils/apis/ErrorApi/MockErrorApi.d.ts:33:5 - (ae-undocumented) Missing documentation for "getErrors".
|
||||
// src/testUtils/apis/ErrorApi/MockErrorApi.d.ts:34:5 - (ae-undocumented) Missing documentation for "waitForError".
|
||||
// src/testUtils/apis/PermissionApi/MockPermissionApi.d.ts:13:5 - (ae-undocumented) Missing documentation for "authorize".
|
||||
// src/testUtils/apis/StorageApi/MockStorageApi.d.ts:19:5 - (ae-undocumented) Missing documentation for "create".
|
||||
// src/testUtils/apis/StorageApi/MockStorageApi.d.ts:20:5 - (ae-undocumented) Missing documentation for "forBucket".
|
||||
// src/testUtils/apis/StorageApi/MockStorageApi.d.ts:21:5 - (ae-undocumented) Missing documentation for "snapshot".
|
||||
// src/testUtils/apis/StorageApi/MockStorageApi.d.ts:22:5 - (ae-undocumented) Missing documentation for "set".
|
||||
// src/testUtils/apis/StorageApi/MockStorageApi.d.ts:23:5 - (ae-undocumented) Missing documentation for "remove".
|
||||
// src/testUtils/apis/StorageApi/MockStorageApi.d.ts:24:5 - (ae-undocumented) Missing documentation for "observe$".
|
||||
// src/testUtils/apis/PermissionApi/MockPermissionApi.d.ts:14:5 - (ae-undocumented) Missing documentation for "authorize".
|
||||
// src/testUtils/apis/StorageApi/MockStorageApi.d.ts:21:5 - (ae-undocumented) Missing documentation for "create".
|
||||
// src/testUtils/apis/StorageApi/MockStorageApi.d.ts:22:5 - (ae-undocumented) Missing documentation for "forBucket".
|
||||
// src/testUtils/apis/StorageApi/MockStorageApi.d.ts:23:5 - (ae-undocumented) Missing documentation for "snapshot".
|
||||
// src/testUtils/apis/StorageApi/MockStorageApi.d.ts:24:5 - (ae-undocumented) Missing documentation for "set".
|
||||
// src/testUtils/apis/StorageApi/MockStorageApi.d.ts:25:5 - (ae-undocumented) Missing documentation for "remove".
|
||||
// src/testUtils/apis/StorageApi/MockStorageApi.d.ts:26:5 - (ae-undocumented) Missing documentation for "observe$".
|
||||
// src/testUtils/apis/mockApis.d.ts:58:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/testUtils/apis/mockApis.d.ts:59:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/testUtils/apis/mockApis.d.ts:125:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/testUtils/apis/mockApis.d.ts:128:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/testUtils/apis/mockApis.d.ts:150:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/testUtils/apis/mockApis.d.ts:158:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/testUtils/apis/mockApis.d.ts:177:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/testUtils/apis/mockApis.d.ts:180:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/testUtils/apis/mockApis.d.ts:197:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/testUtils/apis/mockApis.d.ts:200:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
// src/testUtils/apis/mockApis.d.ts:215:15 - (ae-undocumented) Missing documentation for "factory".
|
||||
// src/testUtils/apis/mockApis.d.ts:216:15 - (ae-undocumented) Missing documentation for "mock".
|
||||
```
|
||||
|
||||
@@ -21,6 +21,7 @@ import { AnalyticsApi, AnalyticsEvent } from '@backstage/core-plugin-api';
|
||||
* Use getEvents in tests to verify captured events.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Use {@link @backstage/test-utils#mockApis.(analytics:namespace)} instead
|
||||
*/
|
||||
export class MockAnalyticsApi implements AnalyticsApi {
|
||||
private events: AnalyticsEvent[] = [];
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ApiFactory } from '@backstage/frontend-plugin-api';
|
||||
import { ApiFactory } from '@backstage/core-plugin-api';
|
||||
|
||||
/**
|
||||
* Represents a mocked version of an API, where you automatically have access to
|
||||
@@ -23,6 +23,7 @@ import { ConfigApi } from '@backstage/core-plugin-api';
|
||||
* that can be used to mock configuration using a plain object.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Use {@link mockApis.(config:namespace)} instead
|
||||
* @example
|
||||
* ```tsx
|
||||
* const mockConfig = new MockConfigApi({
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
* {@link @backstage/plugin-permission-react#PermissionApi}. Supply a
|
||||
* requestHandler function to override the mock result returned for a given
|
||||
* request.
|
||||
* @deprecated Use {@link @backstage/test-utils#mockApis.(permission:namespace)} instead
|
||||
* @public
|
||||
*/
|
||||
export class MockPermissionApi implements PermissionApi {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { StorageApi } from '@backstage/core-plugin-api';
|
||||
import { MockStorageApi } from './MockStorageApi';
|
||||
|
||||
|
||||
@@ -20,12 +20,14 @@ import ObservableImpl from 'zen-observable';
|
||||
|
||||
/**
|
||||
* Type for map holding data in {@link MockStorageApi}
|
||||
* @deprecated Use {@link @backstage/test-utils#mockApis.(storage:namespace)} instead
|
||||
* @public
|
||||
*/
|
||||
export type MockStorageBucket = { [key: string]: any };
|
||||
|
||||
/**
|
||||
* Mock implementation of the {@link core-plugin-api#StorageApi} to be used in tests
|
||||
* @deprecated Use {@link @backstage/test-utils#mockApis.(storage:namespace)} instead
|
||||
* @public
|
||||
*/
|
||||
export class MockStorageApi implements StorageApi {
|
||||
@@ -44,7 +46,21 @@ export class MockStorageApi implements StorageApi {
|
||||
}
|
||||
|
||||
static create(data?: MockStorageBucket) {
|
||||
return new MockStorageApi('', new Map(), data);
|
||||
// Translate a nested data object structure into a flat object with keys
|
||||
// like `/a/b` with their corresponding leaf values
|
||||
const keyValues: { [key: string]: any } = {};
|
||||
function put(value: { [key: string]: any }, namespace: string) {
|
||||
for (const [key, val] of Object.entries(value)) {
|
||||
if (typeof val === 'object' && val !== null) {
|
||||
put(val, `${namespace}/${key}`);
|
||||
} else {
|
||||
const namespacedKey = `${namespace}/${key.replace(/^\//, '')}`;
|
||||
keyValues[namespacedKey] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
put(data ?? {}, '');
|
||||
return new MockStorageApi('', new Map(), keyValues);
|
||||
}
|
||||
|
||||
forBucket(name: string): StorageApi {
|
||||
|
||||
@@ -30,7 +30,10 @@ import { toInternalTranslationRef } from '../../../../../core-plugin-api/src/tra
|
||||
|
||||
const DEFAULT_LANGUAGE = 'en';
|
||||
|
||||
/** @alpha */
|
||||
/**
|
||||
* @alpha
|
||||
* @deprecated Use `mockApis` from `@backstage/test-utils` instead
|
||||
*/
|
||||
export class MockTranslationApi implements TranslationApi {
|
||||
static create() {
|
||||
const i18n = createI18n({
|
||||
|
||||
@@ -20,3 +20,5 @@ export * from './ErrorApi';
|
||||
export * from './FetchApi';
|
||||
export * from './PermissionApi';
|
||||
export * from './StorageApi';
|
||||
export { type ApiMock } from './ApiMock';
|
||||
export { mockApis } from './mockApis';
|
||||
|
||||
@@ -0,0 +1,773 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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.
|
||||
*/
|
||||
|
||||
import {
|
||||
AuthorizeResult,
|
||||
createPermission,
|
||||
} from '@backstage/plugin-permission-common';
|
||||
import { mockApis } from './mockApis';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { StorageValueSnapshot } from '@backstage/core-plugin-api';
|
||||
import { createTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
|
||||
describe('mockApis', () => {
|
||||
describe('analytics', () => {
|
||||
it('can create an instance', () => {
|
||||
const analytics = mockApis.analytics();
|
||||
expect(
|
||||
analytics.captureEvent({
|
||||
action: 'a',
|
||||
subject: 'b',
|
||||
context: { pluginId: 'c', extension: 'd', routeRef: 'e' },
|
||||
}),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('can create a mock and make assertions on it', async () => {
|
||||
expect.assertions(3);
|
||||
const analytics = mockApis.analytics.mock({
|
||||
captureEvent: event => {
|
||||
expect(event).toEqual({
|
||||
action: 'a',
|
||||
subject: 'b',
|
||||
context: { pluginId: 'c', extension: 'd', routeRef: 'e' },
|
||||
});
|
||||
},
|
||||
});
|
||||
expect(
|
||||
analytics.captureEvent({
|
||||
action: 'a',
|
||||
subject: 'b',
|
||||
context: { pluginId: 'c', extension: 'd', routeRef: 'e' },
|
||||
}),
|
||||
).toBeUndefined();
|
||||
expect(analytics.captureEvent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('config', () => {
|
||||
const data = { backend: { baseUrl: 'http://test.com' } };
|
||||
|
||||
it('can create an instance', () => {
|
||||
const empty = mockApis.config();
|
||||
expect(empty.getOptional('backend.baseUrl')).toBeUndefined();
|
||||
|
||||
const notEmpty = mockApis.config({ data });
|
||||
expect(notEmpty.getOptional('backend.baseUrl')).toEqual(
|
||||
'http://test.com',
|
||||
);
|
||||
});
|
||||
|
||||
it('can create a mock and make assertions on it', async () => {
|
||||
const mock = mockApis.config.mock({ getString: () => 'replaced' });
|
||||
expect(mock.getString('a')).toEqual('replaced');
|
||||
expect(mock.getString).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('discovery', () => {
|
||||
it('can create an instance', async () => {
|
||||
const empty = mockApis.discovery();
|
||||
await expect(empty.getBaseUrl('catalog')).resolves.toBe(
|
||||
'http://example.com/api/catalog',
|
||||
);
|
||||
|
||||
const notEmpty = mockApis.discovery({ baseUrl: 'https://other.net' });
|
||||
await expect(notEmpty.getBaseUrl('catalog')).resolves.toBe(
|
||||
'https://other.net/api/catalog',
|
||||
);
|
||||
});
|
||||
|
||||
it('can create a mock and make assertions on it', async () => {
|
||||
const empty = mockApis.discovery.mock();
|
||||
expect(empty.getBaseUrl('catalog')).toBeUndefined();
|
||||
expect(empty.getBaseUrl).toHaveBeenCalledTimes(1);
|
||||
|
||||
const notEmpty = mockApis.discovery.mock({
|
||||
getBaseUrl: async () => 'replaced',
|
||||
});
|
||||
await expect(notEmpty.getBaseUrl('catalog')).resolves.toBe('replaced');
|
||||
expect(notEmpty.getBaseUrl).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('identity', () => {
|
||||
it('can create an instance', async () => {
|
||||
const empty = mockApis.identity();
|
||||
await expect(empty.getBackstageIdentity()).resolves.toEqual({
|
||||
type: 'user',
|
||||
userEntityRef: 'user:default/test',
|
||||
ownershipEntityRefs: ['user:default/test'],
|
||||
});
|
||||
await expect(empty.getCredentials()).resolves.toEqual({});
|
||||
await expect(empty.getProfileInfo()).resolves.toEqual({});
|
||||
await expect(empty.signOut()).resolves.toBeUndefined();
|
||||
|
||||
const notEmpty = mockApis.identity({
|
||||
userEntityRef: 'a',
|
||||
ownershipEntityRefs: ['b'],
|
||||
token: 'c',
|
||||
email: 'd',
|
||||
displayName: 'e',
|
||||
picture: 'f',
|
||||
});
|
||||
await expect(notEmpty.getBackstageIdentity()).resolves.toEqual({
|
||||
type: 'user',
|
||||
userEntityRef: 'a',
|
||||
ownershipEntityRefs: ['b'],
|
||||
});
|
||||
await expect(notEmpty.getCredentials()).resolves.toEqual({ token: 'c' });
|
||||
await expect(notEmpty.getProfileInfo()).resolves.toEqual({
|
||||
email: 'd',
|
||||
displayName: 'e',
|
||||
picture: 'f',
|
||||
});
|
||||
await expect(notEmpty.signOut()).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('can create a mock and make assertions on it', async () => {
|
||||
const empty = mockApis.identity.mock();
|
||||
expect(empty.getBackstageIdentity()).toBeUndefined();
|
||||
expect(empty.getCredentials()).toBeUndefined();
|
||||
expect(empty.getProfileInfo()).toBeUndefined();
|
||||
expect(empty.signOut()).toBeUndefined();
|
||||
expect(empty.getBackstageIdentity).toHaveBeenCalledTimes(1);
|
||||
expect(empty.getCredentials).toHaveBeenCalledTimes(1);
|
||||
expect(empty.getProfileInfo).toHaveBeenCalledTimes(1);
|
||||
expect(empty.signOut).toHaveBeenCalledTimes(1);
|
||||
|
||||
const notEmpty = mockApis.identity.mock({
|
||||
getBackstageIdentity: async () => ({
|
||||
type: 'user',
|
||||
userEntityRef: 'a',
|
||||
ownershipEntityRefs: ['b'],
|
||||
}),
|
||||
getCredentials: async () => ({ token: 'c' }),
|
||||
getProfileInfo: async () => ({
|
||||
email: 'd',
|
||||
displayName: 'e',
|
||||
picture: 'f',
|
||||
}),
|
||||
signOut: async () => undefined,
|
||||
});
|
||||
await expect(notEmpty.getBackstageIdentity()).resolves.toEqual({
|
||||
type: 'user',
|
||||
userEntityRef: 'a',
|
||||
ownershipEntityRefs: ['b'],
|
||||
});
|
||||
await expect(notEmpty.getCredentials()).resolves.toEqual({ token: 'c' });
|
||||
await expect(notEmpty.getProfileInfo()).resolves.toEqual({
|
||||
email: 'd',
|
||||
displayName: 'e',
|
||||
picture: 'f',
|
||||
});
|
||||
await expect(notEmpty.signOut()).resolves.toBeUndefined();
|
||||
expect(notEmpty.getBackstageIdentity).toHaveBeenCalledTimes(1);
|
||||
expect(notEmpty.getCredentials).toHaveBeenCalledTimes(1);
|
||||
expect(notEmpty.getProfileInfo).toHaveBeenCalledTimes(1);
|
||||
expect(notEmpty.signOut).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('permission', () => {
|
||||
it('can create an instance', async () => {
|
||||
// default allow
|
||||
const permission1 = mockApis.permission();
|
||||
await expect(
|
||||
permission1.authorize({
|
||||
permission: createPermission({
|
||||
name: 'permission.1',
|
||||
attributes: {},
|
||||
}),
|
||||
}),
|
||||
).resolves.toEqual({ result: AuthorizeResult.ALLOW });
|
||||
|
||||
// static value
|
||||
const permission2 = mockApis.permission({
|
||||
authorize: AuthorizeResult.DENY,
|
||||
});
|
||||
await expect(
|
||||
permission2.authorize({
|
||||
permission: createPermission({
|
||||
name: 'permission.1',
|
||||
attributes: {},
|
||||
}),
|
||||
}),
|
||||
).resolves.toEqual({ result: AuthorizeResult.DENY });
|
||||
|
||||
// callback form
|
||||
const permission3 = mockApis.permission({
|
||||
authorize: req =>
|
||||
req.permission.name === 'permission.1'
|
||||
? AuthorizeResult.ALLOW
|
||||
: AuthorizeResult.DENY,
|
||||
});
|
||||
await expect(
|
||||
permission3.authorize({
|
||||
permission: createPermission({
|
||||
name: 'permission.1',
|
||||
attributes: {},
|
||||
}),
|
||||
}),
|
||||
).resolves.toEqual({ result: AuthorizeResult.ALLOW });
|
||||
await expect(
|
||||
permission3.authorize({
|
||||
permission: createPermission({
|
||||
name: 'permission.2',
|
||||
attributes: {},
|
||||
}),
|
||||
}),
|
||||
).resolves.toEqual({ result: AuthorizeResult.DENY });
|
||||
});
|
||||
|
||||
it('can create a mock and make assertions on it', async () => {
|
||||
const empty = mockApis.permission.mock();
|
||||
expect(
|
||||
empty.authorize({
|
||||
permission: createPermission({
|
||||
name: 'permission.1',
|
||||
attributes: {},
|
||||
}),
|
||||
}),
|
||||
).toBeUndefined();
|
||||
expect(empty.authorize).toHaveBeenCalledTimes(1);
|
||||
|
||||
const notEmpty = mockApis.permission.mock({
|
||||
authorize: async req => ({
|
||||
result:
|
||||
req.permission.name === 'permission.1'
|
||||
? AuthorizeResult.ALLOW
|
||||
: AuthorizeResult.DENY,
|
||||
}),
|
||||
});
|
||||
await expect(
|
||||
notEmpty.authorize({
|
||||
permission: createPermission({
|
||||
name: 'permission.1',
|
||||
attributes: {},
|
||||
}),
|
||||
}),
|
||||
).resolves.toEqual({ result: AuthorizeResult.ALLOW });
|
||||
await expect(
|
||||
notEmpty.authorize({
|
||||
permission: createPermission({
|
||||
name: 'permission.2',
|
||||
attributes: {},
|
||||
}),
|
||||
}),
|
||||
).resolves.toEqual({ result: AuthorizeResult.DENY });
|
||||
expect(notEmpty.authorize).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('storage', () => {
|
||||
describe('instance deep tests', () => {
|
||||
it('should return undefined for values which are unset', async () => {
|
||||
const storage = mockApis.storage();
|
||||
|
||||
expect(storage.snapshot('myfakekey').value).toBeUndefined();
|
||||
expect(storage.snapshot('myfakekey')).toEqual({
|
||||
key: 'myfakekey',
|
||||
presence: 'absent',
|
||||
value: undefined,
|
||||
newValue: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow the setting and snapshotting of the simple data structures', async () => {
|
||||
const storage = mockApis.storage();
|
||||
|
||||
await storage.set('myfakekey', 'helloimastring');
|
||||
await storage.set('mysecondfakekey', 1234);
|
||||
await storage.set('mythirdfakekey', true);
|
||||
expect(storage.snapshot('myfakekey').value).toBe('helloimastring');
|
||||
expect(storage.snapshot('mysecondfakekey').value).toBe(1234);
|
||||
expect(storage.snapshot('mythirdfakekey').value).toBe(true);
|
||||
expect(storage.snapshot('myfakekey')).toEqual({
|
||||
key: 'myfakekey',
|
||||
presence: 'present',
|
||||
value: 'helloimastring',
|
||||
});
|
||||
expect(storage.snapshot('mysecondfakekey')).toEqual({
|
||||
key: 'mysecondfakekey',
|
||||
presence: 'present',
|
||||
value: 1234,
|
||||
});
|
||||
expect(storage.snapshot('mythirdfakekey')).toEqual({
|
||||
key: 'mythirdfakekey',
|
||||
presence: 'present',
|
||||
value: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow setting of complex datastructures', async () => {
|
||||
const storage = mockApis.storage();
|
||||
|
||||
const mockData = {
|
||||
something: 'here',
|
||||
is: [{ super: { complex: [{ but: 'something', why: true }] } }],
|
||||
};
|
||||
|
||||
await storage.set('myfakekey', mockData);
|
||||
|
||||
expect(storage.snapshot('myfakekey').value).toEqual(mockData);
|
||||
expect(storage.snapshot('myfakekey')).toEqual({
|
||||
key: 'myfakekey',
|
||||
presence: 'present',
|
||||
value: mockData,
|
||||
});
|
||||
});
|
||||
|
||||
it('should subscribe to key changes when setting a new value', async () => {
|
||||
const storage = mockApis.storage();
|
||||
|
||||
const wrongKeyNextHandler = jest.fn();
|
||||
const selectedKeyNextHandler = jest.fn();
|
||||
const mockData = { hello: 'im a great new value' };
|
||||
|
||||
await new Promise<void>(resolve => {
|
||||
storage.observe$<typeof mockData>('correctKey').subscribe({
|
||||
next: (...args) => {
|
||||
selectedKeyNextHandler(...args);
|
||||
resolve();
|
||||
},
|
||||
});
|
||||
|
||||
storage.observe$('wrongKey').subscribe({ next: wrongKeyNextHandler });
|
||||
|
||||
storage.set('correctKey', mockData);
|
||||
});
|
||||
|
||||
expect(wrongKeyNextHandler).not.toHaveBeenCalled();
|
||||
expect(selectedKeyNextHandler).toHaveBeenCalledTimes(1);
|
||||
expect(selectedKeyNextHandler).toHaveBeenCalledWith({
|
||||
key: 'correctKey',
|
||||
presence: 'present',
|
||||
value: mockData,
|
||||
});
|
||||
});
|
||||
|
||||
it('should subscribe to key changes when deleting a value', async () => {
|
||||
const storage = mockApis.storage();
|
||||
|
||||
const wrongKeyNextHandler = jest.fn();
|
||||
const selectedKeyNextHandler = jest.fn();
|
||||
const mockData = { hello: 'im a great new value' };
|
||||
|
||||
storage.set('correctKey', mockData);
|
||||
|
||||
await new Promise<void>(resolve => {
|
||||
storage.observe$('correctKey').subscribe({
|
||||
next: (...args) => {
|
||||
selectedKeyNextHandler(...args);
|
||||
resolve();
|
||||
},
|
||||
});
|
||||
|
||||
storage.observe$('wrongKey').subscribe({ next: wrongKeyNextHandler });
|
||||
|
||||
storage.remove('correctKey');
|
||||
});
|
||||
|
||||
expect(wrongKeyNextHandler).not.toHaveBeenCalled();
|
||||
expect(selectedKeyNextHandler).toHaveBeenCalledTimes(1);
|
||||
expect(selectedKeyNextHandler).toHaveBeenCalledWith({
|
||||
key: 'correctKey',
|
||||
presence: 'absent',
|
||||
value: undefined,
|
||||
newValue: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to create different buckets for different uses', async () => {
|
||||
const rootStorage = mockApis.storage();
|
||||
|
||||
const firstStorage = rootStorage.forBucket('userSettings');
|
||||
const secondStorage = rootStorage.forBucket('profileSettings');
|
||||
const keyName = 'blobby';
|
||||
|
||||
await firstStorage.set(keyName, 'boop');
|
||||
await secondStorage.set(keyName, 'deerp');
|
||||
|
||||
expect(firstStorage.snapshot(keyName)).not.toBe(
|
||||
secondStorage.snapshot(keyName),
|
||||
);
|
||||
expect(firstStorage.snapshot(keyName).value).toBe('boop');
|
||||
expect(secondStorage.snapshot(keyName).value).toBe('deerp');
|
||||
expect(firstStorage.snapshot(keyName)).not.toEqual(
|
||||
secondStorage.snapshot(keyName),
|
||||
);
|
||||
expect(firstStorage.snapshot(keyName)).toEqual({
|
||||
key: keyName,
|
||||
presence: 'present',
|
||||
value: 'boop',
|
||||
});
|
||||
expect(secondStorage.snapshot(keyName)).toEqual({
|
||||
key: keyName,
|
||||
presence: 'present',
|
||||
value: 'deerp',
|
||||
});
|
||||
});
|
||||
|
||||
it('should not clash with other namespaces when creating buckets', async () => {
|
||||
const rootStorage = mockApis.storage();
|
||||
|
||||
// when getting key test2 it will translate to /profile/something/deep/test2
|
||||
const firstStorage = rootStorage
|
||||
.forBucket('profile')
|
||||
.forBucket('something')
|
||||
.forBucket('deep');
|
||||
// when getting key deep/test2 it will translate to /profile/something/deep/test2
|
||||
const secondStorage = rootStorage.forBucket('profile/something');
|
||||
|
||||
await firstStorage.set('test2', { error: true });
|
||||
|
||||
expect(secondStorage.snapshot('deep/test2').value).toBe(undefined);
|
||||
expect(secondStorage.snapshot('deep/test2')).toMatchObject({
|
||||
presence: 'absent',
|
||||
});
|
||||
});
|
||||
|
||||
it('should not reuse storage instances between different rootStorages', async () => {
|
||||
const rootStorage1 = mockApis.storage();
|
||||
const rootStorage2 = mockApis.storage();
|
||||
|
||||
const firstStorage = rootStorage1.forBucket('something');
|
||||
const secondStorage = rootStorage2.forBucket('something');
|
||||
|
||||
await firstStorage.set('test2', true);
|
||||
|
||||
expect(firstStorage.snapshot('test2').value).toBe(true);
|
||||
expect(secondStorage.snapshot('test2').value).toBe(undefined);
|
||||
expect(firstStorage.snapshot('test2')).toEqual({
|
||||
key: 'test2',
|
||||
presence: 'present',
|
||||
value: true,
|
||||
});
|
||||
expect(secondStorage.snapshot('test2')).toEqual({
|
||||
key: 'test2',
|
||||
presence: 'absent',
|
||||
value: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should freeze the snapshot value', async () => {
|
||||
const storage = mockApis.storage();
|
||||
|
||||
const data = { foo: 'bar', baz: [{ foo: 'bar' }] };
|
||||
storage.set('foo', data);
|
||||
|
||||
const snapshot = storage.snapshot<typeof data>('foo');
|
||||
expect(snapshot.value).not.toBe(data);
|
||||
|
||||
if (snapshot.presence !== 'present') {
|
||||
throw new Error('Invalid presence');
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
snapshot.value.foo = 'buzz';
|
||||
}).toThrow(/Cannot assign to read only property/);
|
||||
expect(() => {
|
||||
snapshot.value.baz[0].foo = 'buzz';
|
||||
}).toThrow(/Cannot assign to read only property/);
|
||||
expect(() => {
|
||||
snapshot.value.baz.push({ foo: 'buzz' });
|
||||
}).toThrow(/Cannot add property 1, object is not extensible/);
|
||||
});
|
||||
|
||||
it('should freeze observed values', async () => {
|
||||
const storage = mockApis.storage();
|
||||
|
||||
const snapshotPromise = new Promise<any>(resolve => {
|
||||
storage.observe$('test').subscribe({
|
||||
next: resolve,
|
||||
});
|
||||
});
|
||||
|
||||
storage.set('test', {
|
||||
foo: {
|
||||
bar: 'baz',
|
||||
},
|
||||
});
|
||||
|
||||
const snapshot = await snapshotPromise;
|
||||
expect(snapshot.presence).toBe('present');
|
||||
expect(() => {
|
||||
snapshot.value!.foo.bar = 'qux';
|
||||
}).toThrow(/Cannot assign to read only property 'bar' of object/);
|
||||
});
|
||||
|
||||
it('should JSON serialize stored values', async () => {
|
||||
const storage = mockApis.storage();
|
||||
|
||||
storage.set<any>('test', {
|
||||
foo: {
|
||||
toJSON() {
|
||||
return {
|
||||
bar: 'baz',
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(storage.snapshot('test')).toMatchObject({
|
||||
presence: 'present',
|
||||
value: {
|
||||
foo: {
|
||||
bar: 'baz',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('can create an instance', () => {
|
||||
const empty = mockApis.storage();
|
||||
expect(empty.snapshot('a')).toEqual({ key: 'a', presence: 'absent' });
|
||||
|
||||
const notEmpty = mockApis.storage({ data: { a: 1, b: { c: 2 } } });
|
||||
expect(notEmpty.snapshot('a')).toEqual({
|
||||
key: 'a',
|
||||
presence: 'present',
|
||||
value: 1,
|
||||
});
|
||||
expect(notEmpty.forBucket('b').snapshot('c')).toEqual({
|
||||
key: 'c',
|
||||
presence: 'present',
|
||||
value: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it('can create a mock and make assertions on it', () => {
|
||||
const empty = mockApis.storage.mock();
|
||||
expect(empty.snapshot('a')).toBeUndefined();
|
||||
expect(empty.snapshot).toHaveBeenCalledTimes(1);
|
||||
|
||||
const notEmpty = mockApis.storage.mock({
|
||||
snapshot<T extends JsonValue>(k: string): StorageValueSnapshot<T> {
|
||||
return { key: k, presence: 'present', value: 'v' as T };
|
||||
},
|
||||
});
|
||||
expect(notEmpty.snapshot('a')).toEqual({
|
||||
key: 'a',
|
||||
presence: 'present',
|
||||
value: 'v',
|
||||
});
|
||||
expect(notEmpty.snapshot).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('translation', () => {
|
||||
describe('instance deep tests', () => {
|
||||
function snapshotWithMessages<
|
||||
const TMessages extends { [key in string]: string },
|
||||
>(messages: TMessages) {
|
||||
const translationApi = mockApis.translation();
|
||||
const ref = createTranslationRef({
|
||||
id: 'test',
|
||||
messages,
|
||||
});
|
||||
const snapshot = translationApi.getTranslation(ref);
|
||||
if (!snapshot.ready) {
|
||||
throw new Error('Translation snapshot is not ready');
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
it('should format plain messages', () => {
|
||||
const snapshot = snapshotWithMessages({
|
||||
foo: 'Foo',
|
||||
bar: 'Bar',
|
||||
baz: 'Baz',
|
||||
});
|
||||
|
||||
expect(snapshot.t('foo')).toBe('Foo');
|
||||
expect(snapshot.t('bar')).toBe('Bar');
|
||||
expect(snapshot.t('baz')).toBe('Baz');
|
||||
});
|
||||
|
||||
it('should support interpolation', () => {
|
||||
const snapshot = snapshotWithMessages({
|
||||
shallow: 'Foo {{ bar }}',
|
||||
multiple: 'Foo {{ bar }} {{ baz }}',
|
||||
deep: 'Foo {{ bar.baz }}',
|
||||
});
|
||||
|
||||
// @ts-expect-error
|
||||
expect(snapshot.t('shallow')).toBe('Foo {{ bar }}');
|
||||
expect(snapshot.t('shallow', { bar: 'Bar' })).toBe('Foo Bar');
|
||||
|
||||
// @ts-expect-error
|
||||
expect(snapshot.t('multiple')).toBe('Foo {{ bar }} {{ baz }}');
|
||||
// @ts-expect-error
|
||||
expect(snapshot.t('multiple', { bar: 'Bar' })).toBe(
|
||||
'Foo Bar {{ baz }}',
|
||||
);
|
||||
expect(snapshot.t('multiple', { bar: 'Bar', baz: 'Baz' })).toBe(
|
||||
'Foo Bar Baz',
|
||||
);
|
||||
|
||||
// @ts-expect-error
|
||||
expect(snapshot.t('deep')).toBe('Foo {{ bar.baz }}');
|
||||
expect(snapshot.t('deep', { bar: { baz: 'Baz' } })).toBe('Foo Baz');
|
||||
});
|
||||
|
||||
// Escaping isn't as useful in React, since we don't need to escape HTML in strings
|
||||
it('should not escape by default', () => {
|
||||
const snapshot = snapshotWithMessages({
|
||||
foo: 'Foo {{ foo }}',
|
||||
});
|
||||
|
||||
expect(snapshot.t('foo', { foo: '<div>' })).toBe('Foo <div>');
|
||||
expect(
|
||||
snapshot.t('foo', {
|
||||
foo: '<div>',
|
||||
interpolation: { escapeValue: true },
|
||||
}),
|
||||
).toBe('Foo <div>');
|
||||
});
|
||||
|
||||
it('should support nesting', () => {
|
||||
const snapshot = snapshotWithMessages({
|
||||
foo: 'Foo $t(bar) $t(baz)',
|
||||
bar: 'Nested',
|
||||
baz: 'Baz {{ qux }}',
|
||||
});
|
||||
|
||||
expect(snapshot.t('foo', { qux: 'Deep' })).toBe('Foo Nested Baz Deep');
|
||||
});
|
||||
|
||||
it('should support formatting', () => {
|
||||
const snapshot = snapshotWithMessages({
|
||||
plain: '= {{ x }}',
|
||||
number: '= {{ x, number }}',
|
||||
numberFixed: '= {{ x, number(minimumFractionDigits: 2) }}',
|
||||
relativeTime: '= {{ x, relativeTime }}',
|
||||
relativeSeconds: '= {{ x, relativeTime(second) }}',
|
||||
relativeSecondsShort:
|
||||
'= {{ x, relativeTime(range: second; style: short) }}',
|
||||
list: '= {{ x, list }}',
|
||||
});
|
||||
|
||||
expect(snapshot.t('plain', { x: '5' })).toBe('= 5');
|
||||
expect(snapshot.t('number', { x: 5 })).toBe('= 5');
|
||||
expect(
|
||||
snapshot.t('number', {
|
||||
x: 5,
|
||||
formatParams: { x: { minimumFractionDigits: 1 } },
|
||||
}),
|
||||
).toBe('= 5.0');
|
||||
expect(snapshot.t('numberFixed', { x: 5 })).toBe('= 5.00');
|
||||
expect(
|
||||
snapshot.t('numberFixed', {
|
||||
x: 5,
|
||||
formatParams: { x: { minimumFractionDigits: 3 } },
|
||||
}),
|
||||
).toBe('= 5.000');
|
||||
expect(snapshot.t('relativeTime', { x: 3 })).toBe('= in 3 days');
|
||||
expect(snapshot.t('relativeTime', { x: -3 })).toBe('= 3 days ago');
|
||||
expect(
|
||||
snapshot.t('relativeTime', {
|
||||
x: 15,
|
||||
formatParams: { x: { range: 'weeks' } },
|
||||
}),
|
||||
).toBe('= in 15 weeks');
|
||||
expect(
|
||||
snapshot.t('relativeTime', {
|
||||
x: 15,
|
||||
formatParams: { x: { range: 'weeks', style: 'short' } },
|
||||
}),
|
||||
).toBe('= in 15 wk.');
|
||||
expect(snapshot.t('relativeSeconds', { x: 1 })).toBe('= in 1 second');
|
||||
expect(snapshot.t('relativeSeconds', { x: 2 })).toBe('= in 2 seconds');
|
||||
expect(snapshot.t('relativeSeconds', { x: -3 })).toBe(
|
||||
'= 3 seconds ago',
|
||||
);
|
||||
expect(snapshot.t('relativeSeconds', { x: 0 })).toBe('= in 0 seconds');
|
||||
expect(snapshot.t('relativeSecondsShort', { x: 1 })).toBe(
|
||||
'= in 1 sec.',
|
||||
);
|
||||
expect(snapshot.t('relativeSecondsShort', { x: 2 })).toBe(
|
||||
'= in 2 sec.',
|
||||
);
|
||||
expect(snapshot.t('relativeSecondsShort', { x: -3 })).toBe(
|
||||
'= 3 sec. ago',
|
||||
);
|
||||
expect(snapshot.t('relativeSecondsShort', { x: 0 })).toBe(
|
||||
'= in 0 sec.',
|
||||
);
|
||||
expect(snapshot.t('list', { x: ['a'] })).toBe('= a');
|
||||
expect(snapshot.t('list', { x: ['a', 'b'] })).toBe('= a and b');
|
||||
expect(snapshot.t('list', { x: ['a', 'b', 'c'] })).toBe(
|
||||
'= a, b, and c',
|
||||
);
|
||||
});
|
||||
|
||||
it('should support plurals', () => {
|
||||
const snapshot = snapshotWithMessages({
|
||||
derp_one: 'derp',
|
||||
derp_other: 'derps',
|
||||
derpWithCount_one: '{{ count }} derp',
|
||||
derpWithCount_other: '{{ count }} derps',
|
||||
});
|
||||
|
||||
expect(snapshot.t('derp', { count: 1 })).toBe('derp');
|
||||
expect(snapshot.t('derp', { count: 2 })).toBe('derps');
|
||||
expect(snapshot.t('derp', { count: 0 })).toBe('derps');
|
||||
expect(snapshot.t('derpWithCount', { count: 1 })).toBe('1 derp');
|
||||
expect(snapshot.t('derpWithCount', { count: 2 })).toBe('2 derps');
|
||||
expect(snapshot.t('derpWithCount', { count: 0 })).toBe('0 derps');
|
||||
});
|
||||
});
|
||||
|
||||
it('can create an instance', () => {
|
||||
const translation = mockApis.translation();
|
||||
const ref = createTranslationRef({
|
||||
id: 'test',
|
||||
messages: { a: 'b' },
|
||||
});
|
||||
const result = translation.getTranslation(ref);
|
||||
if (!result.ready) {
|
||||
throw new Error('not ready');
|
||||
}
|
||||
expect(result.t('a')).toEqual('b');
|
||||
});
|
||||
|
||||
it('can create a mock and make assertions on it', () => {
|
||||
const ref = createTranslationRef({
|
||||
id: 'test',
|
||||
messages: { a: 'b' },
|
||||
});
|
||||
|
||||
const empty = mockApis.translation.mock();
|
||||
expect(empty.getTranslation(ref)).toBeUndefined();
|
||||
|
||||
const notEmpty = mockApis.translation.mock({
|
||||
getTranslation: () =>
|
||||
({
|
||||
ready: true,
|
||||
t: () => 'b',
|
||||
} as any),
|
||||
});
|
||||
const result = notEmpty.getTranslation(ref);
|
||||
if (!result.ready) {
|
||||
throw new Error('not ready');
|
||||
}
|
||||
expect(result.t('a')).toEqual('b');
|
||||
expect(notEmpty.getTranslation).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,381 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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.
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import {
|
||||
AnalyticsApi,
|
||||
ApiFactory,
|
||||
ApiRef,
|
||||
ConfigApi,
|
||||
DiscoveryApi,
|
||||
IdentityApi,
|
||||
StorageApi,
|
||||
analyticsApiRef,
|
||||
configApiRef,
|
||||
createApiFactory,
|
||||
discoveryApiRef,
|
||||
identityApiRef,
|
||||
storageApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import {
|
||||
TranslationApi,
|
||||
translationApiRef,
|
||||
} from '@backstage/core-plugin-api/alpha';
|
||||
import {
|
||||
AuthorizeResult,
|
||||
EvaluatePermissionRequest,
|
||||
} from '@backstage/plugin-permission-common';
|
||||
import {
|
||||
PermissionApi,
|
||||
permissionApiRef,
|
||||
} from '@backstage/plugin-permission-react';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { ApiMock } from './ApiMock';
|
||||
import { MockPermissionApi } from './PermissionApi';
|
||||
import { MockStorageApi } from './StorageApi';
|
||||
import { MockTranslationApi } from './TranslationApi';
|
||||
|
||||
/** @internal */
|
||||
function simpleFactory<TApi, TArgs extends unknown[]>(
|
||||
ref: ApiRef<TApi>,
|
||||
factory: (...args: TArgs) => TApi,
|
||||
): (...args: TArgs) => ApiFactory<TApi, TApi, {}> {
|
||||
return (...args) =>
|
||||
createApiFactory({
|
||||
api: ref,
|
||||
deps: {},
|
||||
factory: () => factory(...args),
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function simpleMock<TApi>(
|
||||
ref: ApiRef<TApi>,
|
||||
mockFactory: () => jest.Mocked<TApi>,
|
||||
): (partialImpl?: Partial<TApi>) => ApiMock<TApi> {
|
||||
return partialImpl => {
|
||||
const mock = mockFactory();
|
||||
if (partialImpl) {
|
||||
for (const [key, impl] of Object.entries(partialImpl)) {
|
||||
if (typeof impl === 'function') {
|
||||
(mock as any)[key].mockImplementation(impl);
|
||||
} else {
|
||||
(mock as any)[key] = impl;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Object.assign(mock, {
|
||||
factory: createApiFactory({
|
||||
api: ref,
|
||||
deps: {},
|
||||
factory: () => mock,
|
||||
}),
|
||||
}) as ApiMock<TApi>;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock implementations of the core utility APIs, to be used in tests.
|
||||
*
|
||||
* @public
|
||||
* @remarks
|
||||
*
|
||||
* There are some variations among the APIs depending on what needs tests
|
||||
* might have, but overall there are three main usage patterns:
|
||||
*
|
||||
* 1: Creating an actual fake API instance, often with a simplified version
|
||||
* of functionality, by calling the mock API itself as a function.
|
||||
*
|
||||
* ```ts
|
||||
* // The function often accepts parameters that control its behavior
|
||||
* const foo = mockApis.foo();
|
||||
* ```
|
||||
*
|
||||
* 2: Creating a mock API, where all methods are replaced with jest mocks, by
|
||||
* calling the API's `mock` function.
|
||||
*
|
||||
* ```ts
|
||||
* // You can optionally supply a subset of its methods to implement
|
||||
* const foo = mockApis.foo.mock({
|
||||
* someMethod: () => 'mocked result',
|
||||
* });
|
||||
* // After exercising your test, you can make assertions on the mock:
|
||||
* expect(foo.someMethod).toHaveBeenCalledTimes(2);
|
||||
* expect(foo.otherMethod).toHaveBeenCalledWith(testData);
|
||||
* ```
|
||||
*
|
||||
* 3: Creating an API factory that behaves similarly to the mock as per above.
|
||||
*
|
||||
* ```ts
|
||||
* const factory = mockApis.foo.factory({
|
||||
* someMethod: () => 'mocked result',
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export namespace mockApis {
|
||||
const analyticsMockSkeleton = (): jest.Mocked<AnalyticsApi> => ({
|
||||
captureEvent: jest.fn(),
|
||||
});
|
||||
/**
|
||||
* Mock implementation of {@link @backstage/core-plugin-api#AnalyticsApi}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function analytics(): AnalyticsApi {
|
||||
return analyticsMockSkeleton();
|
||||
}
|
||||
/**
|
||||
* Mock implementations of {@link @backstage/core-plugin-api#AnalyticsApi}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export namespace analytics {
|
||||
export const factory = simpleFactory(analyticsApiRef, analytics);
|
||||
export const mock = simpleMock(analyticsApiRef, analyticsMockSkeleton);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fake implementation of {@link @backstage/core-plugin-api#ConfigApi}
|
||||
* with optional data supplied.
|
||||
*
|
||||
* @public
|
||||
* @example
|
||||
*
|
||||
* ```tsx
|
||||
* const config = mockApis.config({
|
||||
* data: { app: { baseUrl: 'https://example.com' } },
|
||||
* });
|
||||
*
|
||||
* await renderInTestApp(
|
||||
* <TestApiProvider apis={[[configApiRef, config]]}>
|
||||
* <MyTestedComponent />
|
||||
* </TestApiProvider>,
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export function config(options?: { data?: JsonObject }): ConfigApi {
|
||||
return new ConfigReader(options?.data, 'mock-config');
|
||||
}
|
||||
/**
|
||||
* Mock helpers for {@link @backstage/core-plugin-api#ConfigApi}.
|
||||
*
|
||||
* @see {@link @backstage/core-plugin-api#mockApis.config}
|
||||
* @public
|
||||
*/
|
||||
export namespace config {
|
||||
/**
|
||||
* Creates a factory for a fake implementation of
|
||||
* {@link @backstage/core-plugin-api#ConfigApi} with optional
|
||||
* configuration data supplied.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const factory = simpleFactory(configApiRef, config);
|
||||
/**
|
||||
* Creates a mock implementation of
|
||||
* {@link @backstage/core-plugin-api#ConfigApi}. All methods are
|
||||
* replaced with jest mock functions, and you can optionally pass in a
|
||||
* subset of methods with an explicit implementation.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const mock = simpleMock(configApiRef, () => ({
|
||||
has: jest.fn(),
|
||||
keys: jest.fn(),
|
||||
get: jest.fn(),
|
||||
getOptional: jest.fn(),
|
||||
getConfig: jest.fn(),
|
||||
getOptionalConfig: jest.fn(),
|
||||
getConfigArray: jest.fn(),
|
||||
getOptionalConfigArray: jest.fn(),
|
||||
getNumber: jest.fn(),
|
||||
getOptionalNumber: jest.fn(),
|
||||
getBoolean: jest.fn(),
|
||||
getOptionalBoolean: jest.fn(),
|
||||
getString: jest.fn(),
|
||||
getOptionalString: jest.fn(),
|
||||
getStringArray: jest.fn(),
|
||||
getOptionalStringArray: jest.fn(),
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fake implementation of {@link @backstage/core-plugin-api#DiscoveryApi}. By
|
||||
* default returns URLs on the form `http://example.com/api/<pluginIs>`.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function discovery(options?: { baseUrl?: string }): DiscoveryApi {
|
||||
const baseUrl = options?.baseUrl ?? 'http://example.com';
|
||||
return {
|
||||
async getBaseUrl(pluginId: string) {
|
||||
return `${baseUrl}/api/${pluginId}`;
|
||||
},
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Mock implementations of {@link @backstage/core-plugin-api#DiscoveryApi}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export namespace discovery {
|
||||
export const factory = simpleFactory(discoveryApiRef, discovery);
|
||||
export const mock = simpleMock(discoveryApiRef, () => ({
|
||||
getBaseUrl: jest.fn(),
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fake implementation of {@link @backstage/core-plugin-api#IdentityApi}. By
|
||||
* default returns no token or profile info, and the user `user:default/test`.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function identity(options?: {
|
||||
userEntityRef?: string;
|
||||
ownershipEntityRefs?: string[];
|
||||
token?: string;
|
||||
email?: string;
|
||||
displayName?: string;
|
||||
picture?: string;
|
||||
}): IdentityApi {
|
||||
const {
|
||||
userEntityRef = 'user:default/test',
|
||||
ownershipEntityRefs = ['user:default/test'],
|
||||
token,
|
||||
email,
|
||||
displayName,
|
||||
picture,
|
||||
} = options ?? {};
|
||||
return {
|
||||
async getBackstageIdentity() {
|
||||
return { type: 'user', ownershipEntityRefs, userEntityRef };
|
||||
},
|
||||
async getCredentials() {
|
||||
return { token };
|
||||
},
|
||||
async getProfileInfo() {
|
||||
return { email, displayName, picture };
|
||||
},
|
||||
async signOut() {},
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Mock implementations of {@link @backstage/core-plugin-api#IdentityApi}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export namespace identity {
|
||||
export const factory = simpleFactory(identityApiRef, identity);
|
||||
export const mock = simpleMock(
|
||||
identityApiRef,
|
||||
(): jest.Mocked<IdentityApi> => ({
|
||||
getBackstageIdentity: jest.fn(),
|
||||
getCredentials: jest.fn(),
|
||||
getProfileInfo: jest.fn(),
|
||||
signOut: jest.fn(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fake implementation of
|
||||
* {@link @backstage/plugin-permission-react#PermissionApi}. By default allows
|
||||
* all actions.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function permission(options?: {
|
||||
authorize?:
|
||||
| AuthorizeResult.ALLOW
|
||||
| AuthorizeResult.DENY
|
||||
| ((
|
||||
request: EvaluatePermissionRequest,
|
||||
) => AuthorizeResult.ALLOW | AuthorizeResult.DENY);
|
||||
}): PermissionApi {
|
||||
const authorizeInput = options?.authorize;
|
||||
let authorize: (
|
||||
request: EvaluatePermissionRequest,
|
||||
) => AuthorizeResult.ALLOW | AuthorizeResult.DENY;
|
||||
if (authorizeInput === undefined) {
|
||||
authorize = () => AuthorizeResult.ALLOW;
|
||||
} else if (typeof authorizeInput === 'function') {
|
||||
authorize = authorizeInput;
|
||||
} else {
|
||||
authorize = () => authorizeInput;
|
||||
}
|
||||
return new MockPermissionApi(authorize);
|
||||
}
|
||||
/**
|
||||
* Mock implementation of
|
||||
* {@link @backstage/plugin-permission-react#PermissionApi}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export namespace permission {
|
||||
export const factory = simpleFactory(permissionApiRef, permission);
|
||||
export const mock = simpleMock(permissionApiRef, () => ({
|
||||
authorize: jest.fn(),
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fake implementation of {@link @backstage/core-plugin-api#StorageApi}.
|
||||
* Stores data temporarily in memory.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function storage(options?: { data?: JsonObject }): StorageApi {
|
||||
return MockStorageApi.create(options?.data);
|
||||
}
|
||||
/**
|
||||
* Mock implementations of {@link @backstage/core-plugin-api#StorageApi}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export namespace storage {
|
||||
export const factory = simpleFactory(storageApiRef, storage);
|
||||
export const mock = simpleMock(storageApiRef, () => ({
|
||||
forBucket: jest.fn(),
|
||||
set: jest.fn(),
|
||||
remove: jest.fn(),
|
||||
observe$: jest.fn(),
|
||||
snapshot: jest.fn(),
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fake implementation of {@link @backstage/core-plugin-api/alpha#TranslationApi}.
|
||||
* By default returns the default translation.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function translation(): TranslationApi {
|
||||
return MockTranslationApi.create();
|
||||
}
|
||||
/**
|
||||
* Mock implementations of {@link @backstage/core-plugin-api/alpha#TranslationApi}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export namespace translation {
|
||||
export const factory = simpleFactory(translationApiRef, translation);
|
||||
export const mock = simpleMock(translationApiRef, () => ({
|
||||
getTranslation: jest.fn(),
|
||||
translation$: jest.fn(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/core-app-api';
|
||||
import { TableColumn, TableProps } from '@backstage/core-components';
|
||||
import { configApiRef, storageApiRef } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
@@ -28,8 +27,7 @@ import {
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils';
|
||||
import {
|
||||
MockPermissionApi,
|
||||
MockStorageApi,
|
||||
mockApis,
|
||||
TestApiProvider,
|
||||
renderInTestApp,
|
||||
} from '@backstage/test-utils';
|
||||
@@ -72,17 +70,15 @@ describe('DefaultApiExplorerPage', () => {
|
||||
}),
|
||||
});
|
||||
|
||||
const configApi = new ConfigReader({
|
||||
organization: {
|
||||
name: 'My Company',
|
||||
},
|
||||
const configApi = mockApis.config({
|
||||
data: { organization: { name: 'My Company' } },
|
||||
});
|
||||
|
||||
const apiDocsConfig = {
|
||||
getApiDefinitionWidget: () => undefined,
|
||||
};
|
||||
|
||||
const storageApi = MockStorageApi.create();
|
||||
const storageApi = mockApis.storage();
|
||||
|
||||
const renderWrapped = (children: React.ReactNode) =>
|
||||
renderInTestApp(
|
||||
@@ -96,7 +92,7 @@ describe('DefaultApiExplorerPage', () => {
|
||||
new DefaultStarredEntitiesApi({ storageApi }),
|
||||
],
|
||||
[apiDocsConfigRef, apiDocsConfig],
|
||||
[permissionApiRef, new MockPermissionApi()],
|
||||
[permissionApiRef, mockApis.permission()],
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -16,12 +16,16 @@
|
||||
|
||||
import React from 'react';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import { TestApiProvider, renderInTestApp } from '@backstage/test-utils';
|
||||
import {
|
||||
TestApiProvider,
|
||||
renderInTestApp,
|
||||
mockApis,
|
||||
} from '@backstage/test-utils';
|
||||
import { identityApiRef } from '@backstage/core-plugin-api';
|
||||
import { CookieAuthRedirect } from './CookieAuthRedirect';
|
||||
|
||||
describe('CookieAuthRedirect', () => {
|
||||
const identityApiMock = { getCredentials: jest.fn() };
|
||||
const identityApiMock = mockApis.identity.mock();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
+7
-12
@@ -19,7 +19,7 @@ import { screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { CookieAuthRefreshProvider } from './CookieAuthRefreshProvider';
|
||||
import {
|
||||
MockStorageApi,
|
||||
mockApis,
|
||||
TestApiProvider,
|
||||
renderInTestApp,
|
||||
} from '@backstage/test-utils';
|
||||
@@ -30,12 +30,7 @@ import {
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
describe('CookieAuthRefreshProvider', () => {
|
||||
const storageApiMock = MockStorageApi.create();
|
||||
const discoveryApiMock = {
|
||||
getBaseUrl: jest
|
||||
.fn()
|
||||
.mockResolvedValue('http://localhost:7000/api/techdocs'),
|
||||
};
|
||||
const discoveryApiMock = mockApis.discovery();
|
||||
|
||||
function getExpiresAtInFuture() {
|
||||
const tenMinutesInMilliseconds = 10 * 60 * 1000;
|
||||
@@ -51,7 +46,7 @@ describe('CookieAuthRefreshProvider', () => {
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[fetchApiRef, fetchApiMock],
|
||||
[storageApiRef, storageApiMock],
|
||||
[storageApiRef, mockApis.storage()],
|
||||
[discoveryApiRef, discoveryApiMock],
|
||||
]}
|
||||
>
|
||||
@@ -76,7 +71,7 @@ describe('CookieAuthRefreshProvider', () => {
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[fetchApiRef, fetchApiMock],
|
||||
[storageApiRef, storageApiMock],
|
||||
[storageApiRef, mockApis.storage()],
|
||||
[discoveryApiRef, discoveryApiMock],
|
||||
]}
|
||||
>
|
||||
@@ -107,7 +102,7 @@ describe('CookieAuthRefreshProvider', () => {
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[fetchApiRef, fetchApiMock],
|
||||
[storageApiRef, storageApiMock],
|
||||
[storageApiRef, mockApis.storage()],
|
||||
[discoveryApiRef, discoveryApiMock],
|
||||
]}
|
||||
>
|
||||
@@ -119,7 +114,7 @@ describe('CookieAuthRefreshProvider', () => {
|
||||
|
||||
await waitFor(() =>
|
||||
expect(fetchApiMock.fetch).toHaveBeenCalledWith(
|
||||
'http://localhost:7000/api/techdocs/.backstage/auth/v1/cookie',
|
||||
'http://example.com/api/techdocs/.backstage/auth/v1/cookie',
|
||||
{ credentials: 'include' },
|
||||
),
|
||||
);
|
||||
@@ -153,7 +148,7 @@ describe('CookieAuthRefreshProvider', () => {
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[fetchApiRef, fetchApiMock],
|
||||
[storageApiRef, storageApiMock],
|
||||
[storageApiRef, mockApis.storage()],
|
||||
[discoveryApiRef, discoveryApiMock],
|
||||
]}
|
||||
>
|
||||
|
||||
@@ -17,15 +17,11 @@
|
||||
import React from 'react';
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
import { fetchApiRef, discoveryApiRef } from '@backstage/core-plugin-api';
|
||||
import { TestApiProvider } from '@backstage/test-utils';
|
||||
import { TestApiProvider, mockApis } from '@backstage/test-utils';
|
||||
import { useCookieAuthRefresh } from './useCookieAuthRefresh';
|
||||
|
||||
describe('useCookieAuthRefresh', () => {
|
||||
const discoveryApiMock = {
|
||||
getBaseUrl: jest
|
||||
.fn()
|
||||
.mockResolvedValue('http://localhost:7000/api/techdocs'),
|
||||
};
|
||||
const discoveryApiMock = mockApis.discovery();
|
||||
|
||||
const now = 1710316886171;
|
||||
const tenMinutesInMilliseconds = 10 * 60 * 1000;
|
||||
@@ -269,7 +265,7 @@ describe('useCookieAuthRefresh', () => {
|
||||
|
||||
await waitFor(() =>
|
||||
expect(fetchApiMock.fetch).toHaveBeenCalledWith(
|
||||
'http://localhost:7000/api/techdocs/.backstage/auth/v1/cookie',
|
||||
'http://example.com/api/techdocs/.backstage/auth/v1/cookie',
|
||||
{ credentials: 'include' },
|
||||
),
|
||||
);
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils';
|
||||
import {
|
||||
MockAnalyticsApi,
|
||||
mockApis,
|
||||
renderInTestApp,
|
||||
TestApiProvider,
|
||||
TestApiRegistry,
|
||||
@@ -212,9 +212,9 @@ describe('<CatalogGraphCard/>', () => {
|
||||
],
|
||||
}));
|
||||
|
||||
const analyticsSpy = new MockAnalyticsApi();
|
||||
const analyticsApi = mockApis.analytics();
|
||||
await renderInTestApp(
|
||||
<TestApiProvider apis={[[analyticsApiRef, analyticsSpy]]}>
|
||||
<TestApiProvider apis={[[analyticsApiRef, analyticsApi]]}>
|
||||
{wrapper}
|
||||
</TestApiProvider>,
|
||||
{
|
||||
@@ -228,12 +228,14 @@ describe('<CatalogGraphCard/>', () => {
|
||||
expect(await screen.findByText('b:d/c')).toBeInTheDocument();
|
||||
await userEvent.click(await screen.findByText('b:d/c'));
|
||||
|
||||
expect(analyticsSpy.getEvents()[0]).toMatchObject({
|
||||
action: 'click',
|
||||
subject: 'b:d/c',
|
||||
attributes: {
|
||||
to: '/entity/{kind}/{namespace}/{name}',
|
||||
},
|
||||
});
|
||||
expect(analyticsApi.captureEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: 'click',
|
||||
subject: 'b:d/c',
|
||||
attributes: {
|
||||
to: '/entity/{kind}/{namespace}/{name}',
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,7 +23,7 @@ import { analyticsApiRef } from '@backstage/core-plugin-api';
|
||||
import { catalogApiRef, entityRouteRef } from '@backstage/plugin-catalog-react';
|
||||
import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils';
|
||||
import {
|
||||
MockAnalyticsApi,
|
||||
mockApis,
|
||||
renderInTestApp,
|
||||
TestApiProvider,
|
||||
} from '@backstage/test-utils';
|
||||
@@ -227,9 +227,9 @@ describe.skip('<CatalogGraphPage/>', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
const analyticsSpy = new MockAnalyticsApi();
|
||||
const analyticsApi = mockApis.analytics();
|
||||
await renderInTestApp(
|
||||
<TestApiProvider apis={[[analyticsApiRef, analyticsSpy]]}>
|
||||
<TestApiProvider apis={[[analyticsApiRef, analyticsApi]]}>
|
||||
{wrapper}
|
||||
</TestApiProvider>,
|
||||
{
|
||||
@@ -243,10 +243,12 @@ describe.skip('<CatalogGraphPage/>', () => {
|
||||
|
||||
await userEvent.click(screen.getByText('b:d/e'));
|
||||
|
||||
expect(analyticsSpy.getEvents()[0]).toMatchObject({
|
||||
action: 'click',
|
||||
subject: 'b:d/e',
|
||||
});
|
||||
expect(analyticsApi.captureEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: 'click',
|
||||
subject: 'b:d/e',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('should capture analytics event when navigating to entity', async () => {
|
||||
@@ -256,9 +258,9 @@ describe.skip('<CatalogGraphPage/>', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
const analyticsSpy = new MockAnalyticsApi();
|
||||
const analyticsApi = mockApis.analytics();
|
||||
await renderInTestApp(
|
||||
<TestApiProvider apis={[[analyticsApiRef, analyticsSpy]]}>
|
||||
<TestApiProvider apis={[[analyticsApiRef, analyticsApi]]}>
|
||||
{wrapper}
|
||||
</TestApiProvider>,
|
||||
{
|
||||
@@ -274,12 +276,14 @@ describe.skip('<CatalogGraphPage/>', () => {
|
||||
await user.keyboard('{Shift>}');
|
||||
await user.click(screen.getByText('b:d/e'));
|
||||
|
||||
expect(analyticsSpy.getEvents()[0]).toMatchObject({
|
||||
action: 'click',
|
||||
subject: 'b:d/e',
|
||||
attributes: {
|
||||
to: '/entity/b/d/e',
|
||||
},
|
||||
});
|
||||
expect(analyticsApi.captureEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: 'click',
|
||||
subject: 'b:d/e',
|
||||
attributes: {
|
||||
to: '/entity/b/d/e',
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+8
-6
@@ -16,7 +16,7 @@
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { configApiRef } from '@backstage/core-plugin-api';
|
||||
import { MockConfigApi, TestApiProvider } from '@backstage/test-utils';
|
||||
import { mockApis, TestApiProvider } from '@backstage/test-utils';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
@@ -46,7 +46,7 @@ const entities: Entity[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const mockConfigApi = new MockConfigApi({});
|
||||
const mockConfigApi = mockApis.config();
|
||||
const apis = [[configApiRef, mockConfigApi]] as const;
|
||||
|
||||
describe('<PreviewCatalogInfoComponent />', () => {
|
||||
@@ -122,10 +122,12 @@ describe('<PreviewCatalogInfoComponent />', () => {
|
||||
apis={[
|
||||
[
|
||||
configApiRef,
|
||||
new MockConfigApi({
|
||||
catalog: {
|
||||
import: {
|
||||
entityFilename: 'anvil.yaml',
|
||||
mockApis.config({
|
||||
data: {
|
||||
catalog: {
|
||||
import: {
|
||||
entityFilename: 'anvil.yaml',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@
|
||||
|
||||
import { configApiRef, errorApiRef } from '@backstage/core-plugin-api';
|
||||
import { catalogApiRef } from '@backstage/plugin-catalog-react';
|
||||
import { TestApiProvider, MockConfigApi } from '@backstage/test-utils';
|
||||
import { TestApiProvider, mockApis } from '@backstage/test-utils';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
@@ -43,7 +43,7 @@ describe('<StepPrepareCreatePullRequest />', () => {
|
||||
post: jest.fn(),
|
||||
};
|
||||
|
||||
const configApi = new MockConfigApi({});
|
||||
const configApi = mockApis.config();
|
||||
|
||||
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
|
||||
<TestApiProvider
|
||||
|
||||
@@ -21,7 +21,7 @@ import { MockStarredEntitiesApi, starredEntitiesApiRef } from '../../apis';
|
||||
import { FavoriteEntity } from './FavoriteEntity';
|
||||
import { ComponentEntity } from '@backstage/catalog-model';
|
||||
import {
|
||||
MockStorageApi,
|
||||
mockApis,
|
||||
renderInTestApp,
|
||||
TestApiProvider,
|
||||
} from '@backstage/test-utils';
|
||||
@@ -41,14 +41,12 @@ const entity: ComponentEntity = {
|
||||
},
|
||||
};
|
||||
|
||||
const mockStorage = MockStorageApi.create();
|
||||
|
||||
describe('<FavoriteEntity/>', () => {
|
||||
it('should add to favorites', async () => {
|
||||
await renderInTestApp(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[storageApiRef, mockStorage],
|
||||
[storageApiRef, mockApis.storage()],
|
||||
[starredEntitiesApiRef, new MockStarredEntitiesApi()],
|
||||
]}
|
||||
>
|
||||
@@ -79,7 +77,7 @@ describe('<FavoriteEntity/>', () => {
|
||||
await renderInTestApp(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[storageApiRef, mockStorage],
|
||||
[storageApiRef, mockApis.storage()],
|
||||
[starredEntitiesApiRef, starredEntities],
|
||||
]}
|
||||
>
|
||||
|
||||
@@ -34,15 +34,13 @@ import {
|
||||
} from '@backstage/catalog-client';
|
||||
import { catalogApiRef } from '../../api';
|
||||
import {
|
||||
MockStorageApi,
|
||||
TestApiRegistry,
|
||||
mockApis,
|
||||
renderInTestApp,
|
||||
} from '@backstage/test-utils';
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
import {
|
||||
ConfigApi,
|
||||
configApiRef,
|
||||
IdentityApi,
|
||||
identityApiRef,
|
||||
storageApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
@@ -61,15 +59,20 @@ const mockUser: UserEntity = {
|
||||
},
|
||||
};
|
||||
|
||||
const mockConfigApi = {
|
||||
getOptionalString: () => 'Test Company',
|
||||
} as Partial<ConfigApi>;
|
||||
const ownershipEntityRefs = ['user:default/testuser'];
|
||||
|
||||
const mockConfigApi = mockApis.config({
|
||||
data: { organization: { name: 'Test Company' } },
|
||||
});
|
||||
|
||||
const mockCatalogApi = catalogApiMock.mock();
|
||||
jest.spyOn(mockCatalogApi, 'queryEntities');
|
||||
|
||||
const mockIdentityApi = {
|
||||
getBackstageIdentity: jest.fn(),
|
||||
} as Partial<jest.Mocked<IdentityApi>>;
|
||||
const mockIdentityApi = mockApis.identity({
|
||||
userEntityRef: ownershipEntityRefs[0],
|
||||
ownershipEntityRefs,
|
||||
});
|
||||
jest.spyOn(mockIdentityApi, 'getBackstageIdentity');
|
||||
|
||||
const mockStarredEntitiesApi = new MockStarredEntitiesApi();
|
||||
|
||||
@@ -77,11 +80,10 @@ const apis = TestApiRegistry.from(
|
||||
[configApiRef, mockConfigApi],
|
||||
[catalogApiRef, mockCatalogApi],
|
||||
[identityApiRef, mockIdentityApi],
|
||||
[storageApiRef, MockStorageApi.create()],
|
||||
[storageApiRef, mockApis.storage()],
|
||||
[starredEntitiesApiRef, mockStarredEntitiesApi],
|
||||
);
|
||||
|
||||
const ownershipEntityRefs = ['user:default/testuser'];
|
||||
describe('<UserListPicker />', () => {
|
||||
const mockQueryEntitiesImplementation: CatalogApi['queryEntities'] =
|
||||
async request => {
|
||||
@@ -133,19 +135,13 @@ describe('<UserListPicker />', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
mockCatalogApi.getEntityByRef?.mockResolvedValue(mockUser);
|
||||
mockIdentityApi.getBackstageIdentity?.mockResolvedValue({
|
||||
ownershipEntityRefs,
|
||||
type: 'user',
|
||||
userEntityRef: 'user:default/testuser',
|
||||
});
|
||||
|
||||
mockCatalogApi.queryEntities?.mockImplementation(
|
||||
mockQueryEntitiesImplementation,
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders filter groups', async () => {
|
||||
|
||||
+21
-25
@@ -23,11 +23,7 @@ import {
|
||||
useEntityList,
|
||||
} from '../../hooks';
|
||||
import { catalogApiRef } from '../../api';
|
||||
import {
|
||||
ApiRef,
|
||||
IdentityApi,
|
||||
identityApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { ApiRef, identityApiRef } from '@backstage/core-plugin-api';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { useOwnedEntitiesCount } from './useOwnedEntitiesCount';
|
||||
import {
|
||||
@@ -36,12 +32,14 @@ import {
|
||||
EntityUserFilter,
|
||||
} from '../../filters';
|
||||
import { useMountEffect } from '@react-hookz/web';
|
||||
import { mockApis } from '@backstage/test-utils';
|
||||
|
||||
const mockCatalogApi = catalogApiMock.mock();
|
||||
|
||||
const mockGetBackstageIdentity: jest.MockedFn<
|
||||
IdentityApi['getBackstageIdentity']
|
||||
> = jest.fn();
|
||||
const mockIdentityApi = mockApis.identity({
|
||||
ownershipEntityRefs: ['user:default/spiderman', 'user:group/a-group'],
|
||||
userEntityRef: 'user:default/spiderman',
|
||||
});
|
||||
jest.spyOn(mockIdentityApi, 'getBackstageIdentity');
|
||||
|
||||
jest.mock('@backstage/core-plugin-api', () => {
|
||||
const actual = jest.requireActual('@backstage/core-plugin-api');
|
||||
@@ -50,13 +48,9 @@ jest.mock('@backstage/core-plugin-api', () => {
|
||||
useApi: (ref: ApiRef<any>) => {
|
||||
if (ref === catalogApiRef) {
|
||||
return mockCatalogApi;
|
||||
} else if (ref === identityApiRef) {
|
||||
return mockIdentityApi;
|
||||
}
|
||||
if (ref === identityApiRef) {
|
||||
return {
|
||||
getBackstageIdentity: mockGetBackstageIdentity,
|
||||
};
|
||||
}
|
||||
|
||||
return actual.useApi(ref);
|
||||
},
|
||||
};
|
||||
@@ -65,12 +59,6 @@ jest.mock('@backstage/core-plugin-api', () => {
|
||||
describe('useOwnedEntitiesCount', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
mockGetBackstageIdentity.mockResolvedValue({
|
||||
ownershipEntityRefs: ['user:default/spiderman', 'user:group/a-group'],
|
||||
userEntityRef: 'user:default/spiderman',
|
||||
type: 'user',
|
||||
});
|
||||
});
|
||||
|
||||
it(`shouldn't invoke queryEntities when filters are loading`, async () => {
|
||||
@@ -84,7 +72,9 @@ describe('useOwnedEntitiesCount', () => {
|
||||
wrapper: createWrapperWithInitialFilters({}),
|
||||
});
|
||||
|
||||
await waitFor(() => expect(mockGetBackstageIdentity).toHaveBeenCalled());
|
||||
await waitFor(() =>
|
||||
expect(mockIdentityApi.getBackstageIdentity).toHaveBeenCalled(),
|
||||
);
|
||||
|
||||
await expect(
|
||||
waitFor(() => expect(mockCatalogApi.queryEntities).toHaveBeenCalled()),
|
||||
@@ -114,7 +104,9 @@ describe('useOwnedEntitiesCount', () => {
|
||||
}),
|
||||
});
|
||||
|
||||
await waitFor(() => expect(mockGetBackstageIdentity).toHaveBeenCalled());
|
||||
await waitFor(() =>
|
||||
expect(mockIdentityApi.getBackstageIdentity).toHaveBeenCalled(),
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockCatalogApi.queryEntities).toHaveBeenCalledWith({
|
||||
@@ -151,7 +143,9 @@ describe('useOwnedEntitiesCount', () => {
|
||||
}),
|
||||
});
|
||||
|
||||
await waitFor(() => expect(mockGetBackstageIdentity).toHaveBeenCalled());
|
||||
await waitFor(() =>
|
||||
expect(mockIdentityApi.getBackstageIdentity).toHaveBeenCalled(),
|
||||
);
|
||||
|
||||
await expect(
|
||||
waitFor(() => expect(mockCatalogApi.queryEntities).toHaveBeenCalled()),
|
||||
@@ -185,7 +179,9 @@ describe('useOwnedEntitiesCount', () => {
|
||||
}),
|
||||
});
|
||||
|
||||
await waitFor(() => expect(mockGetBackstageIdentity).toHaveBeenCalled());
|
||||
await waitFor(() =>
|
||||
expect(mockIdentityApi.getBackstageIdentity).toHaveBeenCalled(),
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockCatalogApi.queryEntities).toHaveBeenCalledWith({
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { analyticsApiRef, useAnalytics } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
MockAnalyticsApi,
|
||||
mockApis,
|
||||
TestApiRegistry,
|
||||
withLogCollector,
|
||||
} from '@backstage/test-utils';
|
||||
@@ -57,7 +57,7 @@ describe('useEntity', () => {
|
||||
});
|
||||
|
||||
it('should provide entityRef analytics context', () => {
|
||||
const analyticsSpy = new MockAnalyticsApi();
|
||||
const analyticsSpy = mockApis.analytics();
|
||||
const apis = TestApiRegistry.from([analyticsApiRef, analyticsSpy]);
|
||||
const { result } = renderHook(() => useAnalytics(), {
|
||||
wrapper: ({ children }: React.PropsWithChildren<{}>) => (
|
||||
@@ -69,9 +69,13 @@ describe('useEntity', () => {
|
||||
|
||||
result.current.captureEvent('test', 'value');
|
||||
|
||||
expect(analyticsSpy.getEvents()[0]).toMatchObject({
|
||||
context: { entityRef: 'mykind:default/my-entity' },
|
||||
});
|
||||
expect(analyticsSpy.captureEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
context: expect.objectContaining({
|
||||
entityRef: 'mykind:default/my-entity',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -127,7 +131,7 @@ describe('useAsyncEntity', () => {
|
||||
});
|
||||
|
||||
it('should provide entityRef analytics context', () => {
|
||||
const analyticsSpy = new MockAnalyticsApi();
|
||||
const analyticsSpy = mockApis.analytics.mock();
|
||||
const apis = TestApiRegistry.from([analyticsApiRef, analyticsSpy]);
|
||||
const { result } = renderHook(() => useAnalytics(), {
|
||||
wrapper: ({ children }: React.PropsWithChildren<{}>) => (
|
||||
@@ -144,13 +148,13 @@ describe('useAsyncEntity', () => {
|
||||
|
||||
result.current.captureEvent('test', 'value');
|
||||
|
||||
expect(analyticsSpy.getEvents()[0]).toMatchObject({
|
||||
context: { entityRef: 'mykind:default/my-entity' },
|
||||
});
|
||||
expect(analyticsSpy.captureEvent.mock.calls[0][0].context.entityRef).toBe(
|
||||
'mykind:default/my-entity',
|
||||
);
|
||||
});
|
||||
|
||||
it('should omit entityRef analytics context', () => {
|
||||
const analyticsSpy = new MockAnalyticsApi();
|
||||
const analyticsSpy = mockApis.analytics.mock();
|
||||
const apis = TestApiRegistry.from([analyticsApiRef, analyticsSpy]);
|
||||
const { result } = renderHook(() => useAnalytics(), {
|
||||
wrapper: ({ children }: PropsWithChildren<{}>) => (
|
||||
@@ -162,6 +166,8 @@ describe('useAsyncEntity', () => {
|
||||
|
||||
result.current.captureEvent('test', 'value');
|
||||
|
||||
expect(analyticsSpy.getEvents()[0].context).not.toHaveProperty('entityRef');
|
||||
expect(
|
||||
analyticsSpy.captureEvent.mock.calls[0][0].context,
|
||||
).not.toHaveProperty('entityRef');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,14 +18,12 @@ import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import {
|
||||
alertApiRef,
|
||||
ConfigApi,
|
||||
configApiRef,
|
||||
errorApiRef,
|
||||
IdentityApi,
|
||||
identityApiRef,
|
||||
storageApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { MockStorageApi, TestApiProvider } from '@backstage/test-utils';
|
||||
import { TestApiProvider, mockApis } from '@backstage/test-utils';
|
||||
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||
import qs from 'qs';
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
@@ -41,7 +39,6 @@ import {
|
||||
import { EntityListProvider, useEntityList } from './useEntityListProvider';
|
||||
import { useMountEffect } from '@react-hookz/web';
|
||||
import { translationApiRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { MockTranslationApi } from '@backstage/test-utils/alpha';
|
||||
import { EntityListPagination } from '../types';
|
||||
|
||||
const entities: Entity[] = [
|
||||
@@ -67,20 +64,12 @@ const entities: Entity[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const mockConfigApi = {
|
||||
getOptionalString: () => '',
|
||||
} as Partial<ConfigApi>;
|
||||
|
||||
const ownershipEntityRefs = ['user:default/guest'];
|
||||
|
||||
const mockIdentityApi: Partial<IdentityApi> = {
|
||||
getBackstageIdentity: async () => ({
|
||||
type: 'user',
|
||||
userEntityRef: 'user:default/guest',
|
||||
ownershipEntityRefs,
|
||||
}),
|
||||
getCredentials: async () => ({ token: undefined }),
|
||||
};
|
||||
const mockIdentityApi = mockApis.identity({
|
||||
userEntityRef: 'user:default/guest',
|
||||
ownershipEntityRefs,
|
||||
});
|
||||
const mockCatalogApi = catalogApiMock.mock({
|
||||
getEntities: jest.fn().mockResolvedValue({ items: entities }),
|
||||
queryEntities: jest.fn().mockResolvedValue({
|
||||
@@ -108,13 +97,13 @@ const createWrapper =
|
||||
<MemoryRouter initialEntries={[options.location ?? '']}>
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[configApiRef, mockConfigApi],
|
||||
[configApiRef, mockApis.config()],
|
||||
[catalogApiRef, mockCatalogApi],
|
||||
[identityApiRef, mockIdentityApi],
|
||||
[storageApiRef, MockStorageApi.create()],
|
||||
[storageApiRef, mockApis.storage()],
|
||||
[starredEntitiesApiRef, new MockStarredEntitiesApi()],
|
||||
[alertApiRef, { post: jest.fn() }],
|
||||
[translationApiRef, MockTranslationApi.create()],
|
||||
[translationApiRef, mockApis.translation()],
|
||||
[errorApiRef, { error$: jest.fn(), post: jest.fn() }],
|
||||
]}
|
||||
>
|
||||
|
||||
@@ -15,20 +15,17 @@
|
||||
*/
|
||||
|
||||
import { ComponentEntity, RELATION_OWNED_BY } from '@backstage/catalog-model';
|
||||
import { IdentityApi, identityApiRef } from '@backstage/core-plugin-api';
|
||||
import { TestApiProvider } from '@backstage/test-utils';
|
||||
import { identityApiRef } from '@backstage/core-plugin-api';
|
||||
import { TestApiProvider, mockApis } from '@backstage/test-utils';
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { useEntityOwnership } from './useEntityOwnership';
|
||||
|
||||
describe('useEntityOwnership', () => {
|
||||
type MockIdentityApi = jest.Mocked<Pick<IdentityApi, 'getBackstageIdentity'>>;
|
||||
|
||||
const mockIdentityApi: MockIdentityApi = {
|
||||
getBackstageIdentity: jest.fn(),
|
||||
};
|
||||
|
||||
const identityApi = mockIdentityApi as unknown as IdentityApi;
|
||||
const identityApi = mockApis.identity({
|
||||
userEntityRef: 'user:default/user1',
|
||||
ownershipEntityRefs: ['user:default/user1', 'group:default/group1'],
|
||||
});
|
||||
|
||||
const Wrapper = (props: { children?: React.ReactNode }) => (
|
||||
<TestApiProvider apis={[[identityApiRef, identityApi]]}>
|
||||
@@ -64,12 +61,6 @@ describe('useEntityOwnership', () => {
|
||||
|
||||
describe('useEntityOwnership', () => {
|
||||
it('matches ownership via ownership entity refs', async () => {
|
||||
mockIdentityApi.getBackstageIdentity.mockResolvedValue({
|
||||
type: 'user',
|
||||
userEntityRef: 'user:default/user1',
|
||||
ownershipEntityRefs: ['user:default/user1', 'group:default/group1'],
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useEntityOwnership(), {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { MockStorageApi } from '@backstage/test-utils';
|
||||
import { mockApis } from '@backstage/test-utils';
|
||||
import { DefaultStarredEntitiesApi } from './DefaultStarredEntitiesApi';
|
||||
import { performMigrationToTheNewBucket } from './migration';
|
||||
|
||||
@@ -44,7 +44,7 @@ describe('DefaultStarredEntitiesApi', () => {
|
||||
describe('constructor', () => {
|
||||
it('should call migration', () => {
|
||||
const api = new DefaultStarredEntitiesApi({
|
||||
storageApi: MockStorageApi.create(),
|
||||
storageApi: mockApis.storage(),
|
||||
});
|
||||
expect(performMigrationToTheNewBucket).toHaveBeenCalledTimes(1);
|
||||
expect(api).toBeDefined();
|
||||
@@ -54,7 +54,7 @@ describe('DefaultStarredEntitiesApi', () => {
|
||||
it('should notify and toggle starred entities', async () => {
|
||||
const entityRef = 'component:default/mock';
|
||||
|
||||
const storageApi = MockStorageApi.create();
|
||||
const storageApi = mockApis.storage();
|
||||
const storageBucket = storageApi.forBucket('starredEntities');
|
||||
const api = new DefaultStarredEntitiesApi({ storageApi });
|
||||
|
||||
@@ -85,7 +85,7 @@ describe('DefaultStarredEntitiesApi', () => {
|
||||
it('should read starred entities from storage', async () => {
|
||||
const entityRef = 'component:default/mock';
|
||||
|
||||
const storageApi = MockStorageApi.create();
|
||||
const storageApi = mockApis.storage();
|
||||
const storageBucket = storageApi.forBucket('starredEntities');
|
||||
storageBucket.set('entityRefs', [entityRef]);
|
||||
const api = new DefaultStarredEntitiesApi({ storageApi });
|
||||
|
||||
@@ -15,18 +15,18 @@
|
||||
*/
|
||||
|
||||
import { StorageApi } from '@backstage/core-plugin-api';
|
||||
import { MockStorageApi } from '@backstage/test-utils';
|
||||
import { mockApis } from '@backstage/test-utils';
|
||||
import { performMigrationToTheNewBucket } from './migration';
|
||||
|
||||
describe('performMigrationToTheNewBucket', () => {
|
||||
let mockStorage: StorageApi;
|
||||
|
||||
beforeEach(() => {
|
||||
mockStorage = MockStorageApi.create();
|
||||
mockStorage = mockApis.storage();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should migrate', async () => {
|
||||
|
||||
@@ -24,7 +24,11 @@ import {
|
||||
ScmIntegrationsApi,
|
||||
scmIntegrationsApiRef,
|
||||
} from '@backstage/integration-react';
|
||||
import { TestApiProvider, renderInTestApp } from '@backstage/test-utils';
|
||||
import {
|
||||
TestApiProvider,
|
||||
mockApis,
|
||||
renderInTestApp,
|
||||
} from '@backstage/test-utils';
|
||||
import { createFromTemplateRouteRef, viewTechDocRouteRef } from '../../routes';
|
||||
|
||||
import { AboutCard } from './AboutCard';
|
||||
@@ -37,10 +41,6 @@ import { permissionApiRef } from '@backstage/plugin-permission-react';
|
||||
import { AuthorizeResult } from '@backstage/plugin-permission-common';
|
||||
import { SWRConfig } from 'swr';
|
||||
|
||||
const mockAuthorize = jest.fn();
|
||||
|
||||
const mockPermissionApi = { authorize: mockAuthorize };
|
||||
|
||||
describe('<AboutCard />', () => {
|
||||
const catalogApi = catalogApiMock.mock();
|
||||
|
||||
@@ -411,10 +411,6 @@ describe('<AboutCard />', () => {
|
||||
},
|
||||
};
|
||||
|
||||
mockAuthorize.mockImplementation(async () => ({
|
||||
result: AuthorizeResult.ALLOW,
|
||||
}));
|
||||
|
||||
await renderInTestApp(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
@@ -423,7 +419,7 @@ describe('<AboutCard />', () => {
|
||||
ScmIntegrationsApi.fromConfig(new ConfigReader({})),
|
||||
],
|
||||
[catalogApiRef, catalogApi],
|
||||
[permissionApiRef, mockPermissionApi],
|
||||
[permissionApiRef, mockApis.permission()],
|
||||
]}
|
||||
>
|
||||
<EntityProvider entity={entity}>
|
||||
@@ -466,10 +462,6 @@ describe('<AboutCard />', () => {
|
||||
},
|
||||
};
|
||||
|
||||
mockAuthorize.mockImplementation(async () => ({
|
||||
result: AuthorizeResult.DENY,
|
||||
}));
|
||||
|
||||
await renderInTestApp(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
@@ -478,7 +470,10 @@ describe('<AboutCard />', () => {
|
||||
ScmIntegrationsApi.fromConfig(new ConfigReader({})),
|
||||
],
|
||||
[catalogApiRef, catalogApi],
|
||||
[permissionApiRef, mockPermissionApi],
|
||||
[
|
||||
permissionApiRef,
|
||||
mockApis.permission({ authorize: AuthorizeResult.DENY }),
|
||||
],
|
||||
]}
|
||||
>
|
||||
<EntityProvider entity={entity}>
|
||||
@@ -766,9 +761,6 @@ describe('<AboutCard />', () => {
|
||||
namespace: 'default',
|
||||
},
|
||||
};
|
||||
mockAuthorize.mockImplementation(async () => ({
|
||||
result: AuthorizeResult.ALLOW,
|
||||
}));
|
||||
await renderInTestApp(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
@@ -788,7 +780,7 @@ describe('<AboutCard />', () => {
|
||||
),
|
||||
],
|
||||
[catalogApiRef, catalogApi],
|
||||
[permissionApiRef, mockPermissionApi],
|
||||
[permissionApiRef, mockApis.permission()],
|
||||
]}
|
||||
>
|
||||
<EntityProvider entity={entity}>
|
||||
@@ -819,9 +811,6 @@ describe('<AboutCard />', () => {
|
||||
namespace: 'default',
|
||||
},
|
||||
};
|
||||
mockAuthorize.mockImplementation(async () => ({
|
||||
result: AuthorizeResult.DENY,
|
||||
}));
|
||||
await renderInTestApp(
|
||||
<SWRConfig value={{ provider: () => new Map() }}>
|
||||
<TestApiProvider
|
||||
@@ -842,7 +831,10 @@ describe('<AboutCard />', () => {
|
||||
),
|
||||
],
|
||||
[catalogApiRef, catalogApi],
|
||||
[permissionApiRef, mockPermissionApi],
|
||||
[
|
||||
permissionApiRef,
|
||||
mockApis.permission({ authorize: AuthorizeResult.DENY }),
|
||||
],
|
||||
]}
|
||||
>
|
||||
<EntityProvider entity={entity}>
|
||||
|
||||
@@ -17,12 +17,7 @@
|
||||
import { QueryEntitiesInitialRequest } from '@backstage/catalog-client';
|
||||
import { RELATION_OWNED_BY } from '@backstage/catalog-model';
|
||||
import { TableColumn, TableProps } from '@backstage/core-components';
|
||||
import {
|
||||
IdentityApi,
|
||||
identityApiRef,
|
||||
ProfileInfo,
|
||||
storageApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { identityApiRef, storageApiRef } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
catalogApiRef,
|
||||
entityRouteRef,
|
||||
@@ -31,9 +26,8 @@ import {
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { mockBreakpoint } from '@backstage/core-components/testUtils';
|
||||
import {
|
||||
MockPermissionApi,
|
||||
MockStorageApi,
|
||||
TestApiProvider,
|
||||
mockApis,
|
||||
renderInTestApp,
|
||||
} from '@backstage/test-utils';
|
||||
import DashboardIcon from '@material-ui/icons/Dashboard';
|
||||
@@ -54,7 +48,6 @@ describe('DefaultCatalogPage', () => {
|
||||
});
|
||||
afterEach(() => {
|
||||
window.history.replaceState = origReplaceState;
|
||||
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
@@ -166,19 +159,11 @@ describe('DefaultCatalogPage', () => {
|
||||
}),
|
||||
});
|
||||
|
||||
const testProfile: Partial<ProfileInfo> = {
|
||||
const identityApi = mockApis.identity({
|
||||
userEntityRef: 'user:default/guest',
|
||||
ownershipEntityRefs: ['user:default/guest', 'group:default/tools'],
|
||||
displayName: 'Display Name',
|
||||
};
|
||||
const identityApi: Partial<IdentityApi> = {
|
||||
getBackstageIdentity: async () => ({
|
||||
type: 'user',
|
||||
userEntityRef: 'user:default/guest',
|
||||
ownershipEntityRefs: ['user:default/guest', 'group:default/tools'],
|
||||
}),
|
||||
getCredentials: async () => ({ token: undefined }),
|
||||
getProfileInfo: async () => testProfile,
|
||||
};
|
||||
const storageApi = MockStorageApi.create();
|
||||
});
|
||||
|
||||
const renderWrapped = (children: React.ReactNode) =>
|
||||
renderInTestApp(
|
||||
@@ -186,9 +171,9 @@ describe('DefaultCatalogPage', () => {
|
||||
apis={[
|
||||
[catalogApiRef, catalogApi],
|
||||
[identityApiRef, identityApi],
|
||||
[storageApiRef, storageApi],
|
||||
[storageApiRef, mockApis.storage()],
|
||||
[starredEntitiesApiRef, new MockStarredEntitiesApi()],
|
||||
[permissionApiRef, new MockPermissionApi()],
|
||||
[permissionApiRef, mockApis.permission()],
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user