Skip to content

feat(e2e): cluster-free plugin sanity check for the catalog index (RHIDP-13508) - #4967

Open
gustavolira wants to merge 18 commits into
redhat-developer:mainfrom
gustavolira:main
Open

feat(e2e): cluster-free plugin sanity check for the catalog index (RHIDP-13508)#4967
gustavolira wants to merge 18 commits into
redhat-developer:mainfrom
gustavolira:main

Conversation

@gustavolira

@gustavolira gustavolira commented Jun 17, 2026

Copy link
Copy Markdown
Member

Summary

Plugin sanity checking for the RHDH catalog index, implementing RHIDP-13508.

Validates that every plugin the catalog index declares actually loads in the real RHDH backend, with no cluster deployment and no product image. Building on the cluster-free harness (#5005), Playwright boots packages/backend from source — the product's own Backstage line and dynamicPluginsFeatureLoader — and the spec compares the installed set against /api/dynamic-plugins-info/loaded-plugins, failing with the exact list of installed-but-not-loaded plugins.

The index is selected by CATALOG_INDEX_IMAGE (branch-matched nightly index by default; RC index via Gangway --catalog-index-image).

How it works

  • local-harness/catalog-index-refs.sh — extracts dynamic-plugins.default.yaml from the index image and lists every declared package (uncommented entries only), applying plugin-sanity-excludes.txt. Layers are walked top-down: OCI layers are ordered base-first, so an index rebuilt as an overlay would otherwise yield a stale copy of the file. A missing excludes file is fatal rather than silently disabling every exclusion.
  • local-harness/populate-catalog-index.sh — wraps those refs into an install config (the index ships packages disabled: true, so a plain includes would install nothing) and installs via the shared populate.sh, which gained an optional config argument.
  • playwright.plugin-sanity.config.ts — dedicated request-only config; no browser, no frontend dev server.

Guarding against a green-but-empty run

The spec would otherwise only assert installed ⊆ loaded, which stays green when the install silently underran. So:

  • populate-catalog-index.sh records the index image and resolved oci:// package count in dynamic-plugins-root/.catalog-index-refs
  • the spec asserts the installed count matches it, and that the recorded image matches CATALOG_INDEX_IMAGE
  • plugin-sanity gets its own globalSetup requiring that breadcrumb, so a dynamic-plugins-root left over from the curated populate.sh no longer satisfies the guard

Failure reporting

A broken plugin takes the whole backend down by design, so the culprit is made obvious: testing::report_plugin_startup_failures scans pod logs (current + previous containers, so CrashLoopBackOff is covered) and prints a PLUGIN STARTUP FAILURES block saved as an artifact; the cluster-free wrapper greps its own run log for the same signatures.

CI integration

Runs in handle_ocp_nightlyrun_sanity_plugins_check via testing::run_plugin_sanity_check, which follows the testing::run_tests conventions (test_run_tracker, save_overall_result, gzipped JUnit to SHARED_DIR, report artifacts) so Slack reporting includes it like any other run. It does not alter the existing cluster sanity-plugins deployment.

Local runs

CATALOG_INDEX_IMAGE=quay.io/rhdh/plugin-catalog-index:next \
  ./e2e-tests/local-harness/populate-catalog-index.sh
yarn --cwd e2e-tests plugin-sanity

Verification

  • Against :next — 9 declared packages resolved, backend booted, all loaded.
  • Unit tests cover the breadcrumb parser (e2e-tests/unit/catalog-index-expectation.test.ts).
  • oxlint, oxfmt, tsc, vitest, shellcheck and .ci prettier clean.

Scope note

An earlier revision of this PR also made the cluster sanity-plugins deployment derive its enabled plugin set from the same index, replacing the hand-maintained list. That change has a very different risk profile — it swaps a working curated set for a fully derived one, and a single bad plugin fails the whole showcase-sanity-plugins project — so it has been split into a follow-up PR and is not included here. Branch: gustavolira:sanity-plugins-cluster-dynamic.

Jira

@openshift-ci
openshift-ci Bot requested review from rm3l and subhashkhileri June 17, 2026 20:08
@rhdh-qodo-merge

Copy link
Copy Markdown

PR Summary by Qodo

feat(e2e): add Playwright plugin sanity-check test
🧪 Tests ✨ Enhancement ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

Description

• Add Playwright sanity test that parses default.packages.yaml and validates plugin entries.
• Wire the new spec into the SHOWCASE_SANITY_PLUGINS Playwright project for nightly runs.
• Provide a mock default.packages.yaml in repo root to enable local execution.
Diagram

graph TD
  Config["playwright.config.ts"] --> Project["SHOWCASE_SANITY_PLUGINS"] --> Runner["Playwright runner"] --> Test["plugin-sanity-check.spec.ts"] --> Yaml[("default.packages.yaml")] --> Validate["Validate package entries"]
  CI["Nightly CI deployment"] --> Yaml
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Resolve/install validation (require.resolve / dynamic import)
  • ➕ Catches missing deps and broken package exports, not just naming issues
  • ➕ Aligns better with the header comment about “can be loaded”
  • ➖ May be flaky if the test environment doesn’t include all plugin packages
  • ➖ Could increase runtime and require dependency management in CI images
2. Use dynamic plugin installer CLI in the test
  • ➕ Validates the real dynamic-plugin acquisition path end-to-end
  • ➕ Closer to how RHDH consumes plugins in deployment
  • ➖ Adds infra complexity (network access, caching, time)
  • ➖ Harder to keep deterministic in CI without pinning and mirrors
3. Schema/structure-only validation + stronger rules
  • ➕ Keeps the test lightweight and deterministic
  • ➕ Can still catch common config errors (missing fields, duplicates, invalid npm scope/name regex)
  • ➖ Won’t detect runtime load failures or missing packages

Recommendation: The PR’s lightweight approach is reasonable for a nightly “sanity” gate, but the spec currently only checks package-name scoping (starts with @) and disabled-list structure. Consider either (a) tightening the structure validation (required fields, non-empty lists, duplicate detection, npm package-name regex) and adjusting the test header comment to match behavior, or (b) adding an optional require.resolve/import-based check when the environment guarantees plugin deps are present. Full CLI-based installation is higher fidelity but likely too heavy unless the team is ready to invest in caching and determinism.

Files changed (3) +149 / -0

Tests (1) +137 / -0
plugin-sanity-check.spec.tsAdd Playwright spec to validate enabled/disabled plugin entries +137/-0

Add Playwright spec to validate enabled/disabled plugin entries

• Introduces a new Playwright spec that reads 'default.packages.yaml', validates enabled plugin package name format (scoped), and asserts the disabled package list is parseable and well-formed. Adds a 'component=plugins' annotation and emits a simple console summary.

e2e-tests/playwright/e2e/plugin-sanity-check.spec.ts

Other (2) +12 / -0
default.packages.yamlAdd mock default plugin package list for local runs +11/-0

Add mock default plugin package list for local runs

• Adds a repo-root 'default.packages.yaml' with enabled/disabled plugin entries to support local execution of the new Playwright sanity test. Notes that the real file is injected during CI deployment.

default.packages.yaml

playwright.config.tsInclude plugin sanity spec in SHOWCASE_SANITY_PLUGINS project +1/-0

Include plugin sanity spec in SHOWCASE_SANITY_PLUGINS project

• Extends the 'SHOWCASE_SANITY_PLUGINS' project 'testMatch' list to include 'plugin-sanity-check.spec.ts', ensuring it runs as part of the sanity-plugins CI job.

e2e-tests/playwright.config.ts

@github-actions

Copy link
Copy Markdown
Contributor

The container image build workflow finished with status: cancelled.

@codecov

codecov Bot commented Jun 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 59.96%. Comparing base (6bcb014) to head (d3c0e62).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4967      +/-   ##
==========================================
- Coverage   63.69%   59.96%   -3.74%     
==========================================
  Files         123      111      -12     
  Lines        2424     2198     -226     
  Branches      573      551      -22     
==========================================
- Hits         1544     1318     -226     
- Misses        878      879       +1     
+ Partials        2        1       -1     
Flag Coverage Δ
rhdh 59.96% <ø> (-3.74%) ⬇️
Components Coverage Δ
Backend plugins ∅ <ø> (∅)
Backend app 66.66% <ø> (ø)
Frontend app 58.89% <ø> (ø)
Plugin utils ∅ <ø> (∅)
Dynamic plugins utils ∅ <ø> (∅)

Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 6bcb014...d3c0e62. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@rhdh-qodo-merge

rhdh-qodo-merge Bot commented Jun 17, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 📜 Skill insights (0)

Context used

Grey Divider


Remediation recommended

1. No package resolution performed ✓ Resolved 🐞 Bug ≡ Correctness
Description
The test "All enabled packages can be resolved" never resolves/imports any packages; it only checks
that the string starts with "@" and then records success. This can let CI pass even when a listed
plugin package is missing/unresolvable, making the new sanity check misleading.
Code

e2e-tests/playwright/e2e/plugin-sanity-check.spec.ts[R44-83]

+  test("All enabled packages can be resolved", async () => {
+    // Read default.packages.yaml from rhdh repo root
+    const defaultPackagesPath = join(__dirname, "../../../default.packages.yaml");
+    const yamlContent = readFileSync(defaultPackagesPath, "utf8");
+    const config = yaml.parse(yamlContent) as DefaultPackagesConfig;
+
+    const enabledPackages = config.packages.enabled;
+    console.log(`\n📦 Testing ${enabledPackages.length} enabled packages...\n`);
+
+    const results: {
+      package: string;
+      status: "success" | "failed";
+      error?: string;
+    }[] = [];
+
+    for (const pkg of enabledPackages) {
+      const packageName = pkg.package;
+
+      try {
+        // Attempt to resolve the package
+        // Note: We can't actually import dynamic plugins here as they require
+        // a Backstage runtime, but we can at least verify the package name format
+        // and that it's listed in package.json dependencies
+
+        // Validate package name format
+        if (!packageName.startsWith("@")) {
+          throw new Error("Package name must be scoped (start with @)");
+        }
+
+        // For now, just verify the package is properly formatted
+        // Future enhancement: Use @red-hat-developer-hub/cli-module-install-dynamic-plugins
+        // to actually download and verify the plugins load
+
+        results.push({
+          package: packageName,
+          status: "success",
+        });
+
+        console.log(`✅ ${packageName}`);
+      } catch (error) {
Relevance

●● Moderate

Mixed history: team accepts improving E2E assertions/coverage (#3147,#4526) but rejected similar
“add runtime validation” checks (#4519).

PR-#3147
PR-#4526
PR-#4519

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The test’s core loop never calls any module-resolution API; it only validates a prefix and then
pushes a success result, so it cannot detect missing/unresolvable packages. Additionally, the E2E
test package itself does not declare the example plugin packages as dependencies, reinforcing that
the current implementation cannot be performing real resolution.

e2e-tests/playwright/e2e/plugin-sanity-check.spec.ts[44-83]
e2e-tests/package.json[57-71]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The test name/comments say it verifies packages can be resolved/loaded, but the implementation only checks a naming convention (`startsWith('@')`) and always marks entries as `success` if that passes. This can silently miss real breakages (missing package, typo, unpublished artifact), because no `resolve`/`import` is ever attempted.

### Issue Context
- This is a Playwright E2E "sanity" test intended for CI signal quality. If the test claims resolvability but doesn’t actually do it, it provides misleading green runs.

### Fix Focus Areas
- e2e-tests/playwright/e2e/plugin-sanity-check.spec.ts[44-83]

### Suggested fix
Choose one (A is lowest-risk for now):

**A) Rename + adjust messaging to match reality**
- Rename the test to something like `All enabled packages have valid package identifiers`.
- Update the header comment and inline comments to remove "resolve/import" language.
- Update summary logs to reflect "validated format" rather than "loaded/resolved".

**B) Actually resolve packages (only if intended + feasible)**
- Implement a real resolution check (e.g., `createRequire(import.meta.url).resolve(packageName)` or Node’s `import.meta.resolve` where available) and fail on thrown errors.
- If doing this, ensure the packages being checked are expected to be present in the environment (dependencies or fetched artifacts), otherwise the test will become permanently red.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

@github-actions

Copy link
Copy Markdown
Contributor

The container image build workflow finished with status: cancelled.

@github-actions

Copy link
Copy Markdown
Contributor

The container image build workflow finished with status: cancelled.

@github-actions

Copy link
Copy Markdown
Contributor

Image was built and published successfully. It is available at:

gustavolira added a commit to gustavolira/rhdh that referenced this pull request Jun 18, 2026
Implements review findings from PR redhat-developer#4967 code review:

**Correctness fixes:**
- Fix test.beforeAll signature to use test.info() (follows smoke-test.spec.ts pattern)
- Remove unused manifestPath variable in plugin-dynamic-loading.spec.ts

**Code organization:**
- Extract DEFAULT_PACKAGES_PATH constant to avoid duplication
- Move CONFIG_OVERRIDES to module scope for better reusability
- Remove unused catalog-index-parser.ts (can be recreated when needed)

**Type safety:**
- Import and use BackendFeature type for require() calls instead of any
- Improves type safety when loading plugin modules

**Developer experience:**
- Improve console.warn message to explain impact of missing _nodeModulePaths

All changes are low-risk refactorings that improve code quality without
changing behavior. Type checking passes with no errors.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

The container image build workflow finished with status: cancelled.

@github-actions

Copy link
Copy Markdown
Contributor

The container image build workflow finished with status: cancelled.

@github-actions

Copy link
Copy Markdown
Contributor

The container image build workflow finished with status: cancelled.

@github-actions

Copy link
Copy Markdown
Contributor

The container image build workflow finished with status: cancelled.

@github-actions

Copy link
Copy Markdown
Contributor

The container image build workflow finished with status: cancelled.

@github-actions

Copy link
Copy Markdown
Contributor

The container image build and publish workflows were skipped (either due to [skip-build] tag or no relevant changes with existing image).

@gustavolira

Copy link
Copy Markdown
Member Author

/test ?

@gustavolira

Copy link
Copy Markdown
Member Author

/test e2e-ocp-helm-nightly

1 similar comment
@gustavolira

Copy link
Copy Markdown
Member Author

/test e2e-ocp-helm-nightly

@github-actions

Copy link
Copy Markdown
Contributor

Image was built and published successfully. It is available at:

@gustavolira

Copy link
Copy Markdown
Member Author

/test e2e-ocp-helm-nightly

@github-actions

Copy link
Copy Markdown
Contributor

Image was built and published successfully. It is available at:

gustavolira added a commit to gustavolira/rhdh that referenced this pull request Jun 19, 2026
Implements review findings from PR redhat-developer#4967 code review:

**Correctness fixes:**
- Fix test.beforeAll signature to use test.info() (follows smoke-test.spec.ts pattern)
- Remove unused manifestPath variable in plugin-dynamic-loading.spec.ts

**Code organization:**
- Extract DEFAULT_PACKAGES_PATH constant to avoid duplication
- Move CONFIG_OVERRIDES to module scope for better reusability
- Remove unused catalog-index-parser.ts (can be recreated when needed)

**Type safety:**
- Import and use BackendFeature type for require() calls instead of any
- Improves type safety when loading plugin modules

**Developer experience:**
- Improve console.warn message to explain impact of missing _nodeModulePaths

All changes are low-risk refactorings that improve code quality without
changing behavior. Type checking passes with no errors.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

The container image build and publish workflows were skipped (either due to [skip-build] tag or no relevant changes with existing image).

@github-actions

Copy link
Copy Markdown
Contributor

The container image build and publish workflows were skipped (either due to [skip-build] tag or no relevant changes with existing image).

@gustavolira

Copy link
Copy Markdown
Member Author

/test e2e-ocp-helm-nightly

gustavolira and others added 2 commits July 28, 2026 10:38
The check is cluster-free, but it only ran inside handle_ocp_nightly, so it was
gated on a cluster being provisioned for unrelated steps and could only report
at nightly cadence.

Move it to the E2E Cluster-free GitHub Actions workflow as a `plugin-sanity`
job on a plain runner. Every package the index declares resolves anonymously
(install-dynamic-plugins rewrites registry.access.redhat.com/rhdh/ to
quay.io/rhdh/), so no pull secret and no cluster are involved.

- drops testing::run_plugin_sanity_check and its ocp-nightly.sh call site; the
  cluster-based sanity-plugins deployment and its startup-failure report stay
- adds a schedule trigger, since the catalog index is built outside this repo
  and changes without a commit here, plus a workflow_dispatch
  `catalog_index_image` input replacing the Gangway override for RC verification
- extracts the shared setup into .github/actions/setup-cluster-free-harness so
  the two cluster-free jobs do not duplicate it
- extracts the Backstage startup-failure log shapes into
  local-harness/filter-plugin-startup-failures.sh, so the cluster and
  cluster-free paths cannot drift apart

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fixes from a shell/CI, TypeScript and duplication review pass.

Blocking:
- install jq in the cluster-free setup action: catalog-index-refs.sh parses the
  index manifest with it and the Playwright image does not ship it, so the
  populate step failed on every run (verified by running the pinned image)
- the plugin sanity config no longer adopts a backend already on :7007. The spec
  compares dynamic-plugins-root against what the RUNNING backend loaded, so a
  leftover legacy-local backend made it pass while validating nothing
- report startup failures only when the sanity step itself failed; on an earlier
  failure the log does not exist and the step printed a false all-clear
- classify plugins by role.startsWith("frontend") instead of "not backend", so a
  node-library/web-library role is no longer misfiled as frontend and then failed
  for shipping no bundle
- exclude the spec from the any-test project, which collects every spec

Workflow:
- keep the e2e job on its original triggers; paths filters do not gate the new
  schedule/dispatch events
- put the event name in the concurrency group so the nightly cannot cancel a
  push run, and add timeout-minutes to both jobs
- trigger on changes to the composite action, shape-check the dispatch input
  before it reaches GITHUB_OUTPUT, and upload the backend log with the report
- say plainly that schedule only runs from the default branch

Shell:
- restore the "JUnit results file not found" diagnostic lost in the refactor,
  on stderr since the function's stdout is captured
- assign the layer digests before the loop so a missing jq fails loudly instead
  of being reported as a missing default.yaml
- bound the pod log reads with timeout, and drop the single-use filter wrapper
- move the JUnit helpers below run_tests, whose doc block they had orphaned

Naming, duplication and coverage:
- loadManifest -> scanInstalledPlugins (it scans; there is no manifest file),
  PluginError -> FrontendBundleError, drop the unused role/version plumbing
- share dynamicPluginsRoot and the reporter block via local-harness-servers.ts
- reuse parseRefreshToken instead of re-narrowing the session payload inline
- rename the unit test to plugin-loader.test.ts and cover the four previously
  untested exports (49 tests, up from 22)
- yarn plugin-sanity -> yarn e2e:plugin-sanity, matching e2e:legacy-local
- fix the wrong "both flavors share catalog-index-refs.sh" claim and the stale
  alternative that said the index needs a source build

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The first run of the plugin-sanity job failed on "Resolve the catalog index
image" with `Syntax error: "(" unexpected`. Inside a job `container:` the
default shell is `sh` — /usr/bin/dash in the Playwright image — not the `bash`
GitHub uses on the runner host.

Three steps depended on bash: the `[[ ]]`/`=~` in the resolve step, the
`set -o pipefail` guarding the tee pipeline (dash: "Illegal option -o
pipefail"), and the `[[ -s ]]` in the startup-failure report. Declaring the
shell once at the workflow level covers all of them and stops the trap
recurring; verified against the pinned image that dash rejects and bash accepts
these constructs.

Also write the run log inside the checkout: pairing a repo-relative path with
/tmp made the upload-artifact root `/`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

The container image build workflow finished with status: cancelled.

# workspace dependencies; e2e-tests has its own lockfile.
- name: Install dependencies (root)
shell: bash
run: yarn install
…that boot

The first working run of the plugin-sanity job showed the excludes never took
effect: the generated install config `includes` the index's own plugin list, so
omitting a package from the `plugins:` section does not stop it installing. Both
"excluded" orchestrator packages were installed and aborted the backend.

catalog-index-refs.sh grows an --excluded mode, and populate-catalog-index.sh
now writes those refs into the config as `disabled: true` rather than dropping
them. The excludes file documented the old, wrong premise; it now says how the
exclusion is applied.

The same run named five more plugins that abort the backend purely on missing
startup config, so add dummy entries for them next to the existing ldapOrg and
microsoftGraphOrg ones: keycloakOrg, githubOrg, gitlab (used by two modules) and
the github catalog provider.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

The container image build workflow finished with status: cancelled.

Second pass on the dummy config, driven by what the run reported rather than by
guesswork: the shapes now mirror the pluginConfig blocks the catalog index
actually declares.

- the index declares two gitlab provider ids, `default` and `orgProvider`, both
  keyed on ${GITLAB_HOST}; each validates its own host, so both need an entry
- githubOrg resolves its githubUrl against the configured integrations, so add
  an integrations.github entry for github.invalid; without it the provider
  aborts with "There is no GitHub integration that matches"
- give githubOrg the id/orgs the index sets, so the shapes line up

Everything stays on .invalid, so no request can leave the runner.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

The container image build workflow finished with status: cancelled.

The previous commit's integrations.github entry caused a regression: a host
other than github.com must configure apiBaseUrl and rawBaseUrl explicitly, and
without them core.urlReader failed to instantiate, taking catalog, scaffolder,
tech-radar and techdocs down with it.

Add both base URLs, and give gitlab the integration entry it needs for the same
reason the github provider did ("No gitlab integration found that matches
host"). Still all .invalid.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

The container image build workflow finished with status: cancelled.

…ay on

Sonar findings on the plugin sanity check.

Applied: the two length assertions in the classification test now use
toHaveLength, which reports the actual length on failure instead of just the
compared number.

Not applied, deliberately: "Lifecycle scripts are enabled by default in Yarn
v2+" on the root install. The harness backend runs on better-sqlite3 with an
in-memory database (app-config.local-e2e.yaml), and its native binding is
fetched by an install script — disabling scripts would stop the backend booting
in both cluster-free jobs. The line is also the pre-existing one this PR moved
into the composite action, and matches the plain `yarn install` in pr.yaml,
bash-e2e-lint.yaml, e2e-tests-lint.yaml and rulesync-check.yaml. Recorded at the
step so the constraint is not rediscovered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

The container image build workflow finished with status: cancelled.

@github-actions

Copy link
Copy Markdown
Contributor

Image was built and published successfully. It is available at:

Comment thread e2e-tests/playwright/e2e/plugin-dynamic-loading.spec.ts Outdated
Comment thread e2e-tests/playwright/e2e/plugin-dynamic-loading.spec.ts Outdated
Comment thread e2e-tests/playwright/e2e/plugin-dynamic-loading.spec.ts Outdated
Comment thread e2e-tests/playwright/e2e/plugin-dynamic-loading.spec.ts Outdated
Comment thread e2e-tests/playwright/e2e/plugin-dynamic-loading.spec.ts Outdated
Comment thread e2e-tests/playwright/e2e/plugin-dynamic-loading.spec.ts Outdated
Comment thread e2e-tests/playwright/utils/installed-plugins.ts
Comment thread e2e-tests/playwright/e2e/plugin-dynamic-loading.spec.ts Outdated
Comment thread e2e-tests/playwright/utils/installed-plugins.ts
Comment thread .github/workflows/e2e-cluster-free.yaml Outdated
Comment thread .github/workflows/e2e-cluster-free.yaml Outdated
Comment thread e2e-tests/playwright/e2e/plugin-dynamic-loading.spec.ts Outdated
Comment thread e2e-tests/local-harness/app-config.plugin-sanity.yaml
gustavolira and others added 2 commits July 29, 2026 10:37
Findings from rostalan and zdrapela, both e2e-tests approvers.

Stop shipping a test-only config in the product image. Containerfile:206 does
`COPY app-config*.yaml ./` and .dockerignore excludes *.local-e2e.yaml but had
nothing for app-config.plugin-sanity.yaml, so a file whose header reads
"test-only. Never layer this into a production config" was being built into the
image. Moved under e2e-tests/local-harness/, which .dockerignore already
excludes wholesale, rather than adding another ignore entry.

Give cluster-free-only specs their own home. testDir is set once in
playwright.config.ts and no project overrides it, so the spec had to be ignored
in all four catch-all projects, added over two commits because one was missed.
Moved to playwright/cluster-free/ (precedent: playwright/blocked/) and deleted
the four testIgnore entries; verified showcase, showcase-k8s, showcase-operator
and any-test no longer list it.

Split the single test into one per concern, per CONTRIBUTING.MD §6 II ("each
test has to test one and only one thing"). It was one test with six assertions
over four concerns, so any failure named the whole thing.

Give the breadcrumb invariant a single owner: requireCatalogIndexExpectation
throws with the populate command, and the global setup and spec both use it
instead of each re-deriving the check and the message.

Add a per-frontend-plugin HTTP assertion. The scalprum backend logs a warning
and skips a plugin whose dist-scalprum is unusable, yet that plugin still shows
up in loaded-plugins, so neither existing check saw it. The spec now asserts
every dist-scalprum plugin is served at /api/scalprum/plugins. The auth dance
and both endpoints moved behind a DynamicPluginsApi service object.

Workflow: plugin-sanity timeout 45 -> 20 minutes (runs take 5-7), and the
resolved index digest is printed beside the tag for traceability.

Unit tests 49 -> 61, covering the new helpers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The index added three packages the harness cannot pull: homepage,
intelligent-assistant and intelligent-assistant-backend. `skopeo inspect
--no-creds` reports "unauthorized" for the whole repository on quay.io and on
registry.access.redhat.com alike, so the populate step failed outright on a
plain runner.

These are tag-style refs (:1.11.0--x.y.z); all 31 digest-pinned refs the index
declares still resolve anonymously through the CLI's quay.io fallback, so this
is about publication, not the digest-lookup trap the file already warns about.
I checked every declared package rather than the one the log named, so this
should not need another round trip.

The excludes file documented a single category (cannot initialize); it now
documents both, and each new entry says how to re-check and to delete it once
the image is public. Excluding homepage costs real coverage, so that is called
out rather than buried.

Also corrects the README, which claimed every index package resolves
anonymously.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

The container image build workflow finished with status: cancelled.

Both frontend tests assert that a filtered list is empty, which also holds when
the list was empty to begin with - so they would pass while validating nothing.
The scalprum one is the real risk: it only considers plugins that ship a
dist-scalprum bundle.

Assert both sets are non-empty first, the same guard the installed-count
assertion already provides for the index as a whole.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

The container image build workflow finished with status: cancelled.

@github-actions

Copy link
Copy Markdown
Contributor

Image was built and published successfully. It is available at:

gustavolira and others added 2 commits July 29, 2026 11:38
Two review passes over the plugin sanity check.

Corrects an inverted name. dist/remoteEntry.js was called LEGACY_BUNDLE and
described as the legacy format, but dist/remoteEntry.js + mf-manifest.json is
the module-federation layout used by New Frontend System plugins, and
dist-scalprum is the older RHDH-specific one that scalprum-backend serves. The
constant is now MF_REMOTE_ENTRY and the three comments saying "legacy" are
reworded, since they taught the opposite of the truth.

Layering and naming:
- plugin-loader.ts scanned an install directory and never loaded a plugin, and
  it also held two HTTP response parsers whose only caller is the API module.
  Renamed to installed-plugins.ts (filesystem only) and the parsers moved to
  support/api/dynamic-plugins-api.ts.
- DynamicPluginsApi -> RhdhDynamicPluginsApi, matching RhdhRbacApi and the other
  classes in support/api/.
- scalprumPluginNames was a static method on a class whose other method needs
  construction. It is now a plain fetchScalprumPluginNames function: the
  endpoint belongs to a different backend plugin and needs no credentials, so
  that test no longer pays for a guest auth round trip.
- requireDynamicPluginsPopulated's runCommand parameter receives a yarn script
  name, not a command; renamed to yarnScript.

Duplication:
- isUnknownArray was a second copy of the narrowing guards.ts already owns;
  guards.ts now exports the guard and toUnknownArray delegates to it.
- one readJsonFile and one prop/stringProp pair replace two hand-rolled JSON
  reads and three copies of the "object has a string property" narrowing.
- the two report-then-assert blocks in the spec collapse into missingFrom().
- repeated path literals are named constants, which also fixes the same artifact
  being addressed as join(p, "a", "b") in one place and has("a/b") in another.
- validateFrontendBundle's "missing package.json" branch was unreachable, since
  scanInstalledPlugins skips directories without one.

Correctness:
- readCatalogIndexExpectation now rejects a zero, negative or fractional count.
  A populate run that miscounts to 0 used to be accepted and surfaced later as a
  confusing count mismatch instead of at its cause.
- the breadcrumb test can no longer skip in CI; only a local run without
  CATALOG_INDEX_IMAGE skips it.

Coverage 61 -> 75 tests, driven by mutation testing that found five surviving
mutants: catalogIndexPopulateCommand had no test at all, the package.json name
fallback and the non-directory skip were unpinned, and the digest test never
exercised the key/value re-join it claimed to. RhdhDynamicPluginsApi had no unit
test; it now has four against a fake APIRequestContext, verified to fail when
the Authorization header is dropped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…elector

Shell and CI review of the plugin sanity check.

app-config.plugin-sanity.yaml pointed microsoftGraphOrg at the real
https://graph.microsoft.com/v1.0, the only routable host in a file whose own
header says the check never talks to these services. With a 15s initialDelay the
provider fired on every run, and @azure/identity authenticates against
`authority`, which defaults to login.microsoftonline.com, so the nightly was
making outbound auth calls to Microsoft with a bogus tenant from a shared runner
IP. Both are now .invalid.

testing::report_plugin_startup_failures read the global RELEASE_NAME instead of
taking it as an argument (SC2153), unlike every sibling in that file. In a
namespace with a different release the selector matches nothing and the function
printed "No dynamic-plugin startup failures found", which is the reassuring
message for exactly the silent miss it exists to catch. It now takes the release
name and warns when no pods matched.

The recorded catalog index digest was not the digest that was validated: the
skopeo call omitted the --override-os/--override-arch that catalog-index-refs.sh
uses, so it returned the child manifest rather than the tag's manifest list, and
off linux/amd64 it failed outright into "unresolved".

Both the script header and the excludes file documented a
`!plugin-name` ref suffix the published index does not emit. Following that doc
when adding an exclusion produces a pattern that can never match, so the plugin
installs anyway and nothing reports it.

Also: name the excludes file when it is the reason no packages are left, and
correct the defaults.run.shell comment, which described the symptom rather than
what requesting bash actually changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@gustavolira
gustavolira requested review from rostalan and zdrapela July 29, 2026 14:43
@github-actions

Copy link
Copy Markdown
Contributor

Image was built and published successfully. It is available at:

@gustavolira

Copy link
Copy Markdown
Member Author

/agentic-review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants