Skip to content

Add STP, STD, and tests for CNV-72112: VM IP address filtering#41

Open
guyoron1 wants to merge 1 commit intoRedHatQE:mainfrom
guyoron1:CNV-72112-vm-ip-filter-stp
Open

Add STP, STD, and tests for CNV-72112: VM IP address filtering#41
guyoron1 wants to merge 1 commit intoRedHatQE:mainfrom
guyoron1:CNV-72112-vm-ip-filter-stp

Conversation

@guyoron1
Copy link
Collaborator

@guyoron1 guyoron1 commented Feb 25, 2026

STP Metadata

Jira issue: CNV-72112
PR: kubevirt-ui/kubevirt-plugin#3164
SIG: sig-ui

What this PR does

Adds complete test planning and implementation artifacts for CNV-72112 (VM IP Address Filtering in OCP Console Across All Projects):

STP (stps/sig-ui/vm-ip-filter-stp.md):

  • Full Software Test Plan covering the bug fix for IP address filtering when "All Projects" is selected
  • 15 test scenarios (12 Tier 1, 3 Tier 2) with requirements traceability to CNV-72112, CNV-70478, CNV-66469

STD Stubs (stds/sig-ui/):

  • Go/Ginkgo test stubs (12 PendingIt blocks) for Tier 1 design review
  • Python/pytest test stubs (3 test methods) for Tier 2 design review
  • PSE-format docstrings (Preconditions/Steps/Expected)

Working Tests (tests/sig-ui/):

  • vm_ip_filter_test.go - 12 Tier 1 Go/Ginkgo tests using kubevirt/kubevirt patterns (Ordered context, BeforeAll/AfterAll, cross-namespace VM setup)
  • test_vm_ip_filter_e2e.py + conftest.py - 3 Tier 2 Python/pytest tests using openshift-python-wrapper patterns (VirtualMachineForTests, context managers, cross-namespace IP search)

Test Coverage

Category Count Framework
IP filter (exact match) 1 Go/Ginkgo
IP filter (no match) 1 Go/Ginkgo
IP filter (partial match) 1 Go/Ginkgo
Name filter regression 1 Go/Ginkgo
Label filter regression 1 Go/Ginkgo
Proxy fallback path 1 Go/Ginkgo
Pagination 1 Go/Ginkgo
Select-all/deselect-all 2 Go/Ginkgo
Empty state & summary 2 Go/Ginkgo
Single-project view 1 Go/Ginkgo
Cross-namespace E2E 1 Python/pytest
VM lifecycle E2E 1 Python/pytest
Bulk action E2E 1 Python/pytest

Special notes for your reviewer

  • This is a bug fix STP (no VEP). The fix changes matchedVMS to filter from filteredData instead of raw vms in VirtualMachinesList.tsx
  • Tests validate IP filtering at the API level (not UI automation), mirroring the console's filtering logic
  • The fix is specific to the release-4.18 branch; IP filter was removed in 4.19+

Summary by CodeRabbit

  • Tests
    • Added comprehensive test suites for VM IP address filtering across namespaces, including cross-namespace discovery, VM lifecycle visibility, and bulk action scenarios
    • Introduced test fixtures and helper utilities supporting multi-namespace VM IP filtering validation
    • Added quality engineering test plan documentation for VM IP address filtering

Add complete test artifacts for CNV-72112 (VM IP Address Filtering in
OCP Console Across All Projects) under sig-ui:

- STP: stps/sig-ui/vm-ip-filter-stp.md
- STD stubs: stds/sig-ui/ (Go + Python design review stubs)
- Tests: tests/sig-ui/ (12 Tier 1 Go/Ginkgo + 3 Tier 2 Python/pytest)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@openshift-virtualization-qe-bot-3

Report bugs in Issues

Welcome! 🎉

This pull request will be automatically processed with the following features:

🔄 Automatic Actions

  • Reviewer Assignment: Reviewers are automatically assigned based on the OWNERS file in the repository root
  • Size Labeling: PR size labels (XS, S, M, L, XL, XXL) are automatically applied based on changes
  • Issue Creation: A tracking issue is created for this PR and will be closed when the PR is merged or closed
  • Branch Labeling: Branch-specific labels are applied to track the target branch
  • Auto-verification: Auto-verified users have their PRs automatically marked as verified
  • Labels: Enabled categories: branch, can-be-merged, cherry-pick, has-conflicts, hold, needs-rebase, size, verified, wip

📋 Available Commands

PR Status Management

  • /wip - Mark PR as work in progress (adds WIP: prefix to title)
  • /wip cancel - Remove work in progress status
  • /hold - Block PR merging (approvers only)
  • /hold cancel - Unblock PR merging
  • /verified - Mark PR as verified
  • /verified cancel - Remove verification status
  • /reprocess - Trigger complete PR workflow reprocessing (useful if webhook failed or configuration changed)
  • /regenerate-welcome - Regenerate this welcome message

Review & Approval

  • /lgtm - Approve changes (looks good to me)
  • /approve - Approve PR (approvers only)
  • /assign-reviewers - Assign reviewers based on OWNERS file
  • /assign-reviewer @username - Assign specific reviewer
  • /check-can-merge - Check if PR meets merge requirements

Testing & Validation

  • /retest tox - Run Python test suite with tox
  • /retest all - Run all available tests

Cherry-pick Operations

  • /cherry-pick <branch> - Schedule cherry-pick to target branch when PR is merged
    • Multiple branches: /cherry-pick branch1 branch2 branch3

Label Management

  • /<label-name> - Add a label to the PR
  • /<label-name> cancel - Remove a label from the PR

✅ Merge Requirements

This PR will be automatically approved when the following conditions are met:

  1. Approval: /approve from at least one approver
  2. LGTM Count: Minimum 2 /lgtm from reviewers
  3. Status Checks: All required status checks must pass
  4. No Blockers: No WIP, hold, conflict labels
  5. Verified: PR must be marked as verified (if verification is enabled)

📊 Review Process

Approvers and Reviewers

Approvers:

  • rnetser

Reviewers:

  • rnetser
Available Labels
  • hold
  • verified
  • wip
  • lgtm
  • approve

💡 Tips

  • WIP Status: Use /wip when your PR is not ready for review
  • Verification: The verified label is automatically removed on each new commit
  • Cherry-picking: Cherry-pick labels are processed when the PR is merged
  • Permission Levels: Some commands require approver permissions
  • Auto-verified Users: Certain users have automatic verification and merge privileges

For more information, please refer to the project documentation or contact the maintainers.

@coderabbitai
Copy link

coderabbitai bot commented Feb 25, 2026

Walkthrough

This pull request introduces comprehensive test infrastructure for VM IP address filtering functionality. It adds test stubs, end-to-end test implementations, test fixtures, and a detailed test plan across both Python and Go, covering cross-namespace VM discovery, VM lifecycle verification, and bulk operations filtered by IP address.

Changes

Cohort / File(s) Summary
Python Test Infrastructure
tests/sig-ui/conftest.py, tests/sig-ui/test_vm_ip_filter_e2e.py, stds/sig-ui/test_vm_ip_filter_e2e_stubs.py
Adds pytest fixtures for multi-namespace VM setup, target IP retrieval, and VMI matching helper function. Introduces end-to-end test classes verifying cross-namespace IP filtering, VM lifecycle visibility via IP filters, and bulk actions on IP-filtered VMs. Includes test stubs with detailed docstrings for intended behavior.
Go Test Infrastructure
stds/sig-ui/vm_ip_filter_stubs_test.go, tests/sig-ui/vm_ip_filter_test.go
Introduces Ginkgo-based test suites with placeholder tests and comprehensive implementations. Provides helper filter functions (exact and partial IP matching), setup/cleanup logic, and test cases covering cross-namespace filtering, empty states, pagination, selection behavior, and label/field selector filtering.
Testing Documentation
stps/sig-ui/vm-ip-filter-stp.md
Adds Quality Engineering Test Plan document outlining feature overview, acceptance criteria, testing strategy, environment requirements, risks, and traceability to requirements (CNV-72112, CNV-70478, CNV-66469).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the primary changes: adding test planning (STP), test design documentation (STD), and test implementations for CNV-72112 VM IP address filtering.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@stds/sig-ui/vm_ip_filter_stubs_test.go`:
- Around line 45-235: The test stubs use the invalid Ginkgo v2 identifier
PendingIt; replace every occurrence of PendingIt(...) with PIt(...) (e.g., the
pending specs named "[test_id:TS-CNV-72112-001]" through
"[test_id:TS-CNV-72112-012]") so the pending tests use the correct Ginkgo v2
syntax; update each PendingIt invocation to PIt keeping the same arguments and
Skip(...) body intact.

In `@stps/sig-ui/vm-ip-filter-stp.md`:
- Line 82: Update the "Automation Testing" table entries that currently state
"Playwright-based UI tests. Existing test at
`playwright/tests/tier1/vm-actions.spec.ts`..." to accurately reflect this PR's
executable coverage: replace with a short description like "API-level automated
tests using Go/Ginkgo and Python/pytest; see relevant Ginkgo test(s) and pytest
modules covering the VM IP filter scenarios" and update any test file references
to the actual Go/Ginkgo and Python/pytest test files in this PR; apply the same
replacement for both occurrences currently mentioning Playwright (the row at
line ~82 and the similar row at ~114). Ensure the text clearly indicates
API-level coverage and points to the concrete test suites used in this PR.

In `@tests/sig-ui/conftest.py`:
- Around line 83-87: get_vmis_matching_ip is using substring matching
(any(target_ip in addr ...)) which can produce false positives; change the check
to exact equality against each address (e.g., compare target_ip == addr for addr
in ip_addresses), and ensure you handle None/empty entries consistently
(strip/normalize if needed) when iterating over iface.get("ipAddresses", []) in
the loop that looks up interfaces and appends matched vmi.

In `@tests/sig-ui/test_vm_ip_filter_e2e.py`:
- Around line 109-111: The LOGGER.info call is using an unnecessary f-string
(Ruff F541); change the LOGGER.info invocation that currently has f"Verifying
decoy VMs are not returned in IP filter results" to a plain string literal by
removing the leading "f" so it becomes "Verifying decoy VMs are not returned in
IP filter results" (locate the LOGGER.info(...) statement in
test_vm_ip_filter_e2e.py).
- Around line 217-223: The current immediate post-delete check uses
get_vmis_matching_ip and sets vm_still_present by matching vmi.name == vm_name
OR vmi.namespace == namespace.name which is race-prone and can match unrelated
VMIs; change this to poll with a timeout/retry (e.g., exponential backoff or
test helper wait_until) after deletion and consider a VMI present only if both
vmi.name == vm_name AND vmi.namespace == namespace.name, i.e., replace the OR
with AND and loop until get_vmis_matching_ip returns no VMI that matches both
vm_name and namespace.name or until timeout, failing the test only after the
timeout elapses.

In `@tests/sig-ui/vm_ip_filter_test.go`:
- Around line 45-56: filterVMIsByIP can append the same VMI multiple times
because breaking from the inner loop only exits the IP loop; update the logic in
filterVMIsByIP to ensure each vmi is appended at most once by using a found flag
(e.g., matchedFlag) when you detect a match in either iface.IP or iface.IPs,
append vmi once and then break out of the outer loop over vmi.Status.Interfaces
(or return/continue to next vmi) so subsequent interfaces cannot append the same
vmi again.
- Around line 389-399: The current error handling for
virtClient.VirtualMachineInstance(...).List and
virtClient.VirtualMachine(...).List treats every error as "namespace missing"
and returns, hiding real failures; add the import alias apierrors
"k8s.io/apimachinery/pkg/api/errors" and change each err check to explicitly
test apierrors.IsNotFound(err) and return only in that case, otherwise call
Expect(err).ToNot(HaveOccurred()): update the error branches around
VirtualMachineInstance(...).List and VirtualMachine(...).List to use
apierrors.IsNotFound(err) before returning so RBAC/transport/API errors surface
via Expect.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ae42e3c and d7337fa.

📒 Files selected for processing (6)
  • stds/sig-ui/test_vm_ip_filter_e2e_stubs.py
  • stds/sig-ui/vm_ip_filter_stubs_test.go
  • stps/sig-ui/vm-ip-filter-stp.md
  • tests/sig-ui/conftest.py
  • tests/sig-ui/test_vm_ip_filter_e2e.py
  • tests/sig-ui/vm_ip_filter_test.go

Comment on lines +45 to +235
PendingIt("[test_id:TS-CNV-72112-001] should return only the VM matching the IP address filter", func() {
Skip("Phase 1: Design only - awaiting implementation")
})

/*
Preconditions:
- At least one VM running in the cluster
- IP address 192.0.2.99 not assigned to any VM

Steps:
1. List all VMIs across namespaces
2. Filter by non-existent IP address (192.0.2.99)
3. Verify no VMIs are matched

Expected:
- Filter with non-existent IP returns zero results
*/
PendingIt("[test_id:TS-CNV-72112-002] should display empty state when filtering by non-existent IP", func() {
Skip("Phase 1: Design only - awaiting implementation")
})

/*
Preconditions:
- Multiple VMs with IPs sharing a common prefix (e.g., 10.128.x.x)

Steps:
1. List all VMIs across namespaces
2. Filter by partial IP prefix substring
3. Verify matching VMs are shown

Expected:
- Partial IP filter returns all VMs with matching IP substring
*/
PendingIt("[test_id:TS-CNV-72112-003] should return VMs matching partial IP address substring", func() {
Skip("Phase 1: Design only - awaiting implementation")
})
})

Context("Regression: existing filters after fix", func() {

/*
Preconditions:
- VMs with unique names across namespaces

Steps:
1. List VMs with field selector for target name
2. Verify only matching VM is returned

Expected:
- Name filter returns only VMs matching the entered name
*/
PendingIt("[test_id:TS-CNV-72112-004] should return the correct VM when filtering by name", func() {
Skip("Phase 1: Design only - awaiting implementation")
})

/*
Preconditions:
- VMs with distinct labels (e.g., env=production, env=staging)

Steps:
1. List VMs with label selector for env=production
2. Verify only production VMs are shown

Expected:
- Label filter returns only VMs with the matching label
*/
PendingIt("[test_id:TS-CNV-72112-005] should return VMs matching the selected label", func() {
Skip("Phase 1: Design only - awaiting implementation")
})
})

Context("Fallback path: proxy pod not active", func() {

/*
Preconditions:
- Proxy pod is not running (isProxyPodAlive === false)
- VMs running with assigned IP addresses

Steps:
1. List VMIs in a single namespace (non-proxy path)
2. Filter by target IP
3. Verify correct VM is shown

Expected:
- IP filter works when proxy pod is not active
*/
PendingIt("[test_id:TS-CNV-72112-006] should use frontend-filtered data when proxy pod is not active", func() {
Skip("Phase 1: Design only - awaiting implementation")
})
})

Context("Pagination and selection with filtered data", func() {

/*
Preconditions:
- 5+ VMs across different namespaces

Steps:
1. List all VMIs and record total count
2. Filter by target IP
3. Verify filtered count is less than total

Expected:
- Pagination item count matches the number of filtered VMs
*/
PendingIt("[test_id:TS-CNV-72112-007] should update pagination item count after filtering", func() {
Skip("Phase 1: Design only - awaiting implementation")
})

/*
Preconditions:
- Multiple VMs with varied IPs across namespaces

Steps:
1. Filter VMIs by target IP
2. Build selection set from filtered results
3. Verify selection scope matches filtered count

Expected:
- Select-all selects only VMs currently displayed in the filtered list
*/
PendingIt("[test_id:TS-CNV-72112-008] should select only the filtered VMs when select-all is clicked", func() {
Skip("Phase 1: Design only - awaiting implementation")
})

/*
Preconditions:
- VMs selected via select-all on filtered list

Steps:
1. Clear selection set
2. Verify no VMs are selected

Expected:
- Deselect-all removes all VM selections
*/
PendingIt("[test_id:TS-CNV-72112-009] should clear all VM selections when deselect-all is clicked", func() {
Skip("Phase 1: Design only - awaiting implementation")
})
})

Context("Empty state and summary component", func() {

/*
Preconditions:
- No VMs running in any accessible namespace

Steps:
1. List VMs in an empty namespace
2. Verify list is empty

Expected:
- Empty state component is rendered with actionable content
*/
PendingIt("[test_id:TS-CNV-72112-010] should display the empty state component when no VMs exist", func() {
Skip("Phase 1: Design only - awaiting implementation")
})

/*
Preconditions:
- Exactly 3 VMs created across 2 namespaces

Steps:
1. List all VMs across namespaces
2. Verify total count matches expected

Expected:
- Summary component shows the correct total VM count
*/
PendingIt("[test_id:TS-CNV-72112-011] should display the correct VM count in the summary component", func() {
Skip("Phase 1: Design only - awaiting implementation")
})
})

Context("Single-project view", func() {

/*
Preconditions:
- At least 2 VMs in a single namespace with different IPs

Steps:
1. List VMIs in a single namespace
2. Filter by target IP
3. Verify correct VM shown

Expected:
- IP filter returns the correct VM within a single project
*/
PendingIt("[test_id:TS-CNV-72112-012] should return the correct VM when filtering by IP within a single project", func() {
Skip("Phase 1: Design only - awaiting implementation")
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for PendingIt usage, local definitions, and Ginkgo version
echo "=== Searching for PendingIt usage ==="
rg -n '\bPendingIt\s*\(' --type go | head -20

echo ""
echo "=== Searching for local PendingIt definition ==="
rg -n '^\s*func\s+PendingIt\s*\(' --type go

echo ""
echo "=== Checking Ginkgo version in go.mod ==="
rg -n 'github.com/onsi/ginkgo' go.mod

Repository: RedHatQE/openshift-virtualization-tests-design-docs

Length of output: 2117


🏁 Script executed:

#!/bin/bash
# Check repository structure and file context
echo "=== Repository structure ==="
find . -type f -name "*.go" | head -10

echo ""
echo "=== Checking for go.mod or go.sum anywhere ==="
fd -e mod -e sum | head -10

echo ""
echo "=== Checking file header and imports for vm_ip_filter_stubs_test.go ==="
head -50 stds/sig-ui/vm_ip_filter_stubs_test.go

echo ""
echo "=== Checking README or docs ==="
ls -la | grep -i readme
cat README.md 2>/dev/null | head -30

Repository: RedHatQE/openshift-virtualization-tests-design-docs

Length of output: 2939


Replace PendingIt with PIt to reflect valid Ginkgo v2 syntax in this design stub.

While this repository is design documentation that won't be compiled, the file imports Ginkgo v2 and should demonstrate correct syntax. PendingIt is not valid in Ginkgo v2; use PIt(...) instead for pending tests.

Suggested fix pattern (apply to all 12 occurrences)
- PendingIt("[test_id:TS-CNV-72112-001] should return only the VM matching the IP address filter", func() {
- 	Skip("Phase 1: Design only - awaiting implementation")
- })
+ PIt("[test_id:TS-CNV-72112-001] should return only the VM matching the IP address filter")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
PendingIt("[test_id:TS-CNV-72112-001] should return only the VM matching the IP address filter", func() {
Skip("Phase 1: Design only - awaiting implementation")
})
/*
Preconditions:
- At least one VM running in the cluster
- IP address 192.0.2.99 not assigned to any VM
Steps:
1. List all VMIs across namespaces
2. Filter by non-existent IP address (192.0.2.99)
3. Verify no VMIs are matched
Expected:
- Filter with non-existent IP returns zero results
*/
PendingIt("[test_id:TS-CNV-72112-002] should display empty state when filtering by non-existent IP", func() {
Skip("Phase 1: Design only - awaiting implementation")
})
/*
Preconditions:
- Multiple VMs with IPs sharing a common prefix (e.g., 10.128.x.x)
Steps:
1. List all VMIs across namespaces
2. Filter by partial IP prefix substring
3. Verify matching VMs are shown
Expected:
- Partial IP filter returns all VMs with matching IP substring
*/
PendingIt("[test_id:TS-CNV-72112-003] should return VMs matching partial IP address substring", func() {
Skip("Phase 1: Design only - awaiting implementation")
})
})
Context("Regression: existing filters after fix", func() {
/*
Preconditions:
- VMs with unique names across namespaces
Steps:
1. List VMs with field selector for target name
2. Verify only matching VM is returned
Expected:
- Name filter returns only VMs matching the entered name
*/
PendingIt("[test_id:TS-CNV-72112-004] should return the correct VM when filtering by name", func() {
Skip("Phase 1: Design only - awaiting implementation")
})
/*
Preconditions:
- VMs with distinct labels (e.g., env=production, env=staging)
Steps:
1. List VMs with label selector for env=production
2. Verify only production VMs are shown
Expected:
- Label filter returns only VMs with the matching label
*/
PendingIt("[test_id:TS-CNV-72112-005] should return VMs matching the selected label", func() {
Skip("Phase 1: Design only - awaiting implementation")
})
})
Context("Fallback path: proxy pod not active", func() {
/*
Preconditions:
- Proxy pod is not running (isProxyPodAlive === false)
- VMs running with assigned IP addresses
Steps:
1. List VMIs in a single namespace (non-proxy path)
2. Filter by target IP
3. Verify correct VM is shown
Expected:
- IP filter works when proxy pod is not active
*/
PendingIt("[test_id:TS-CNV-72112-006] should use frontend-filtered data when proxy pod is not active", func() {
Skip("Phase 1: Design only - awaiting implementation")
})
})
Context("Pagination and selection with filtered data", func() {
/*
Preconditions:
- 5+ VMs across different namespaces
Steps:
1. List all VMIs and record total count
2. Filter by target IP
3. Verify filtered count is less than total
Expected:
- Pagination item count matches the number of filtered VMs
*/
PendingIt("[test_id:TS-CNV-72112-007] should update pagination item count after filtering", func() {
Skip("Phase 1: Design only - awaiting implementation")
})
/*
Preconditions:
- Multiple VMs with varied IPs across namespaces
Steps:
1. Filter VMIs by target IP
2. Build selection set from filtered results
3. Verify selection scope matches filtered count
Expected:
- Select-all selects only VMs currently displayed in the filtered list
*/
PendingIt("[test_id:TS-CNV-72112-008] should select only the filtered VMs when select-all is clicked", func() {
Skip("Phase 1: Design only - awaiting implementation")
})
/*
Preconditions:
- VMs selected via select-all on filtered list
Steps:
1. Clear selection set
2. Verify no VMs are selected
Expected:
- Deselect-all removes all VM selections
*/
PendingIt("[test_id:TS-CNV-72112-009] should clear all VM selections when deselect-all is clicked", func() {
Skip("Phase 1: Design only - awaiting implementation")
})
})
Context("Empty state and summary component", func() {
/*
Preconditions:
- No VMs running in any accessible namespace
Steps:
1. List VMs in an empty namespace
2. Verify list is empty
Expected:
- Empty state component is rendered with actionable content
*/
PendingIt("[test_id:TS-CNV-72112-010] should display the empty state component when no VMs exist", func() {
Skip("Phase 1: Design only - awaiting implementation")
})
/*
Preconditions:
- Exactly 3 VMs created across 2 namespaces
Steps:
1. List all VMs across namespaces
2. Verify total count matches expected
Expected:
- Summary component shows the correct total VM count
*/
PendingIt("[test_id:TS-CNV-72112-011] should display the correct VM count in the summary component", func() {
Skip("Phase 1: Design only - awaiting implementation")
})
})
Context("Single-project view", func() {
/*
Preconditions:
- At least 2 VMs in a single namespace with different IPs
Steps:
1. List VMIs in a single namespace
2. Filter by target IP
3. Verify correct VM shown
Expected:
- IP filter returns the correct VM within a single project
*/
PendingIt("[test_id:TS-CNV-72112-012] should return the correct VM when filtering by IP within a single project", func() {
Skip("Phase 1: Design only - awaiting implementation")
})
PIt("[test_id:TS-CNV-72112-001] should return only the VM matching the IP address filter")
PIt("[test_id:TS-CNV-72112-002] should display empty state when filtering by non-existent IP")
PIt("[test_id:TS-CNV-72112-003] should return VMs matching partial IP address substring")
})
Context("Regression: existing filters after fix", func() {
PIt("[test_id:TS-CNV-72112-004] should return the correct VM when filtering by name")
PIt("[test_id:TS-CNV-72112-005] should return VMs matching the selected label")
})
Context("Fallback path: proxy pod not active", func() {
PIt("[test_id:TS-CNV-72112-006] should use frontend-filtered data when proxy pod is not active")
})
Context("Pagination and selection with filtered data", func() {
PIt("[test_id:TS-CNV-72112-007] should update pagination item count after filtering")
PIt("[test_id:TS-CNV-72112-008] should select only the filtered VMs when select-all is clicked")
PIt("[test_id:TS-CNV-72112-009] should clear all VM selections when deselect-all is clicked")
})
Context("Empty state and summary component", func() {
PIt("[test_id:TS-CNV-72112-010] should display the empty state component when no VMs exist")
PIt("[test_id:TS-CNV-72112-011] should display the correct VM count in the summary component")
})
Context("Single-project view", func() {
PIt("[test_id:TS-CNV-72112-012] should return the correct VM when filtering by IP within a single project")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@stds/sig-ui/vm_ip_filter_stubs_test.go` around lines 45 - 235, The test stubs
use the invalid Ginkgo v2 identifier PendingIt; replace every occurrence of
PendingIt(...) with PIt(...) (e.g., the pending specs named
"[test_id:TS-CNV-72112-001]" through "[test_id:TS-CNV-72112-012]") so the
pending tests use the correct Ginkgo v2 syntax; update each PendingIt invocation
to PIt keeping the same arguments and Skip(...) body intact.

| Item | Description | Applicable (Y/N or N/A) | Comments |
|:-----|:------------|:------------------------|:---------|
| Functional Testing | Validates that the feature works according to specified requirements and user stories | Y | Core testing of IP address filtering across all projects with proxy pod active. Verify correct VMs are returned and no false matches occur. |
| Automation Testing | Ensures test cases are automated for continuous integration and regression coverage | Y | Playwright-based UI tests. Existing test at `playwright/tests/tier1/vm-actions.spec.ts` line 98 covers this scenario. |
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Automation strategy should reflect the actual test implementations in this PR.

Line 82 and Line 114 currently describe Playwright UI automation only, while this PR’s executable coverage is Go/Ginkgo + Python/pytest at API level. Please align this section to avoid traceability drift.

📝 Suggested doc update
-| Automation Testing | Ensures test cases are automated for continuous integration and regression coverage | Y | Playwright-based UI tests. Existing test at `playwright/tests/tier1/vm-actions.spec.ts` line 98 covers this scenario. |
+| Automation Testing | Ensures test cases are automated for continuous integration and regression coverage | Y | API-level automated tests in Go/Ginkgo (Tier 1) and Python/pytest (Tier 2) validate console filtering semantics for this fix scope. |

-| **Test Framework** | Playwright (UI tests, Tier 1), pytest (Tier 2 / Python) |
+| **Test Framework** | Go/Ginkgo (Tier 1 API-level), pytest (Tier 2 API-level) |

Also applies to: 114-114

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@stps/sig-ui/vm-ip-filter-stp.md` at line 82, Update the "Automation Testing"
table entries that currently state "Playwright-based UI tests. Existing test at
`playwright/tests/tier1/vm-actions.spec.ts`..." to accurately reflect this PR's
executable coverage: replace with a short description like "API-level automated
tests using Go/Ginkgo and Python/pytest; see relevant Ginkgo test(s) and pytest
modules covering the VM IP filter scenarios" and update any test file references
to the actual Go/Ginkgo and Python/pytest test files in this PR; apply the same
replacement for both occurrences currently mentioning Playwright (the row at
line ~82 and the similar row at ~114). Ensure the text clearly indicates
API-level coverage and points to the concrete test suites used in this PR.

Comment on lines +83 to +87
interfaces = vmi.instance.status.interfaces or []
for iface in interfaces:
ip_addresses = iface.get("ipAddresses", [])
if any(target_ip in addr for addr in ip_addresses):
matched.append(vmi)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use exact IP equality in get_vmis_matching_ip to avoid false positives.

Line 86 currently does substring matching. That can over-match and make exact-IP tests flaky or incorrect.

🔧 Proposed fix
     for vmi in all_vmis:
         interfaces = vmi.instance.status.interfaces or []
         for iface in interfaces:
             ip_addresses = iface.get("ipAddresses", [])
-            if any(target_ip in addr for addr in ip_addresses):
+            primary_ip = iface.get("ip")
+            if primary_ip == target_ip or any(addr == target_ip for addr in ip_addresses):
                 matched.append(vmi)
                 break
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
interfaces = vmi.instance.status.interfaces or []
for iface in interfaces:
ip_addresses = iface.get("ipAddresses", [])
if any(target_ip in addr for addr in ip_addresses):
matched.append(vmi)
interfaces = vmi.instance.status.interfaces or []
for iface in interfaces:
ip_addresses = iface.get("ipAddresses", [])
primary_ip = iface.get("ip")
if primary_ip == target_ip or any(addr == target_ip for addr in ip_addresses):
matched.append(vmi)
break
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/sig-ui/conftest.py` around lines 83 - 87, get_vmis_matching_ip is using
substring matching (any(target_ip in addr ...)) which can produce false
positives; change the check to exact equality against each address (e.g.,
compare target_ip == addr for addr in ip_addresses), and ensure you handle
None/empty entries consistently (strip/normalize if needed) when iterating over
iface.get("ipAddresses", []) in the loop that looks up interfaces and appends
matched vmi.

Comment on lines +109 to +111
LOGGER.info(
f"Verifying decoy VMs are not returned in IP filter results"
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, verify the file exists and read lines 109-111
if [ -f "tests/sig-ui/test_vm_ip_filter_e2e.py" ]; then
  echo "=== File exists ==="
  echo "=== Lines 109-111 ==="
  sed -n '109,111p' tests/sig-ui/test_vm_ip_filter_e2e.py | cat -n
else
  echo "File not found"
fi

Repository: RedHatQE/openshift-virtualization-tests-design-docs

Length of output: 261


🏁 Script executed:

#!/bin/bash
# Run Ruff check to verify F541 finding
if [ -f "tests/sig-ui/test_vm_ip_filter_e2e.py" ]; then
  echo "=== Ruff F541 check ==="
  ruff check tests/sig-ui/test_vm_ip_filter_e2e.py --select F541 2>&1
else
  echo "File not found for Ruff check"
fi

Repository: RedHatQE/openshift-virtualization-tests-design-docs

Length of output: 762


Remove the unnecessary f prefix to satisfy Ruff F541.

Line 110 is an f-string without interpolation and should have the f prefix removed.

🔧 Proposed fix
-        LOGGER.info(
-            f"Verifying decoy VMs are not returned in IP filter results"
-        )
+        LOGGER.info(
+            "Verifying decoy VMs are not returned in IP filter results"
+        )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
LOGGER.info(
f"Verifying decoy VMs are not returned in IP filter results"
)
LOGGER.info(
"Verifying decoy VMs are not returned in IP filter results"
)
🧰 Tools
🪛 Ruff (0.15.2)

[error] 110-110: f-string without any placeholders

Remove extraneous f prefix

(F541)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/sig-ui/test_vm_ip_filter_e2e.py` around lines 109 - 111, The
LOGGER.info call is using an unnecessary f-string (Ruff F541); change the
LOGGER.info invocation that currently has f"Verifying decoy VMs are not returned
in IP filter results" to a plain string literal by removing the leading "f" so
it becomes "Verifying decoy VMs are not returned in IP filter results" (locate
the LOGGER.info(...) statement in test_vm_ip_filter_e2e.py).

Comment on lines +217 to +223
matched_after_delete = get_vmis_matching_ip(
admin_client=admin_client, target_ip=vm_ip_str
)
vm_still_present = any(
vmi.name == vm_name or vmi.namespace == namespace.name
for vmi in matched_after_delete
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Post-delete assertion is race-prone and can false-fail.

The check runs immediately after deletion and uses name OR namespace, which can flag unrelated VMIs as stale results.

🔧 Proposed fix
+import time
@@
-        matched_after_delete = get_vmis_matching_ip(
-            admin_client=admin_client, target_ip=vm_ip_str
-        )
-        vm_still_present = any(
-            vmi.name == vm_name or vmi.namespace == namespace.name
-            for vmi in matched_after_delete
-        )
+        vm_still_present = True
+        for _ in range(30):
+            matched_after_delete = get_vmis_matching_ip(
+                admin_client=admin_client, target_ip=vm_ip_str
+            )
+            vm_still_present = any(
+                vmi.name == vm_name and vmi.namespace == namespace.name
+                for vmi in matched_after_delete
+            )
+            if not vm_still_present:
+                break
+            time.sleep(2)
         assert not vm_still_present, (
             f"Deleted VM {vm_name} still appears in IP filter results "
             f"for {vm_ip_str}. Stale data detected."
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/sig-ui/test_vm_ip_filter_e2e.py` around lines 217 - 223, The current
immediate post-delete check uses get_vmis_matching_ip and sets vm_still_present
by matching vmi.name == vm_name OR vmi.namespace == namespace.name which is
race-prone and can match unrelated VMIs; change this to poll with a
timeout/retry (e.g., exponential backoff or test helper wait_until) after
deletion and consider a VMI present only if both vmi.name == vm_name AND
vmi.namespace == namespace.name, i.e., replace the OR with AND and loop until
get_vmis_matching_ip returns no VMI that matches both vm_name and namespace.name
or until timeout, failing the test only after the timeout elapses.

Comment on lines +45 to +56
for _, iface := range vmi.Status.Interfaces {
if iface.IP == targetIP {
matched = append(matched, vmi)
break
}
for _, ip := range iface.IPs {
if ip == targetIP {
matched = append(matched, vmi)
break
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

filterVMIsByIP can append the same VMI more than once.

When a match is found in iface.IPs, the break only exits the IP loop. The next interface can append the same VMI again.

🔧 Proposed fix
 func filterVMIsByIP(vmis []v1.VirtualMachineInstance, targetIP string) []v1.VirtualMachineInstance {
 	var matched []v1.VirtualMachineInstance
 	for _, vmi := range vmis {
+		found := false
 		for _, iface := range vmi.Status.Interfaces {
 			if iface.IP == targetIP {
-				matched = append(matched, vmi)
-				break
+				found = true
+				break
 			}
 			for _, ip := range iface.IPs {
 				if ip == targetIP {
-					matched = append(matched, vmi)
+					found = true
 					break
 				}
 			}
+			if found {
+				break
+			}
+		}
+		if found {
+			matched = append(matched, vmi)
 		}
 	}
 	return matched
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for _, iface := range vmi.Status.Interfaces {
if iface.IP == targetIP {
matched = append(matched, vmi)
break
}
for _, ip := range iface.IPs {
if ip == targetIP {
matched = append(matched, vmi)
break
}
}
}
func filterVMIsByIP(vmis []v1.VirtualMachineInstance, targetIP string) []v1.VirtualMachineInstance {
var matched []v1.VirtualMachineInstance
for _, vmi := range vmis {
found := false
for _, iface := range vmi.Status.Interfaces {
if iface.IP == targetIP {
found = true
break
}
for _, ip := range iface.IPs {
if ip == targetIP {
found = true
break
}
}
if found {
break
}
}
if found {
matched = append(matched, vmi)
}
}
return matched
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/sig-ui/vm_ip_filter_test.go` around lines 45 - 56, filterVMIsByIP can
append the same VMI multiple times because breaking from the inner loop only
exits the IP loop; update the logic in filterVMIsByIP to ensure each vmi is
appended at most once by using a found flag (e.g., matchedFlag) when you detect
a match in either iface.IP or iface.IPs, append vmi once and then break out of
the outer loop over vmi.Status.Interfaces (or return/continue to next vmi) so
subsequent interfaces cannot append the same vmi again.

Comment on lines +389 to +399
if err != nil {
// Namespace may not exist, which effectively means empty state
By("Namespace does not exist, which is equivalent to empty VM list")
return
}

vmList, err := virtClient.VirtualMachine(emptyNamespace).List(ctx, metav1.ListOptions{})
if err != nil {
By("Namespace does not exist for VM list, empty state applies")
return
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n tests/sig-ui/vm_ip_filter_test.go | sed -n '380,410p'

Repository: RedHatQE/openshift-virtualization-tests-design-docs

Length of output: 1614


🏁 Script executed:

head -20 tests/sig-ui/vm_ip_filter_test.go

Repository: RedHatQE/openshift-virtualization-tests-design-docs

Length of output: 531


🏁 Script executed:

head -35 tests/sig-ui/vm_ip_filter_test.go

Repository: RedHatQE/openshift-virtualization-tests-design-docs

Length of output: 1000


🏁 Script executed:

grep -n "apierrors\|IsNotFound" tests/sig-ui/vm_ip_filter_test.go

Repository: RedHatQE/openshift-virtualization-tests-design-docs

Length of output: 77


🏁 Script executed:

grep -n "GetTestNamespace\|emptyNamespace\|namespace.Create\|namespace.Install" tests/sig-ui/vm_ip_filter_test.go | head -20

Repository: RedHatQE/openshift-virtualization-tests-design-docs

Length of output: 420


🏁 Script executed:

rg "apierrors\." tests/ -l | head -10

Repository: RedHatQE/openshift-virtualization-tests-design-docs

Length of output: 77


🏁 Script executed:

rg "IsNotFound|IsNotPermitted|StatusError" tests/sig-ui/ -n | head -20

Repository: RedHatQE/openshift-virtualization-tests-design-docs

Length of output: 77


🏁 Script executed:

rg "apierrors\." . --type go -l | head -10

Repository: RedHatQE/openshift-virtualization-tests-design-docs

Length of output: 77


🏁 Script executed:

rg "if err != nil" tests/sig-ui/vm_ip_filter_test.go -B2 -A2 | head -50

Repository: RedHatQE/openshift-virtualization-tests-design-docs

Length of output: 612


🏁 Script executed:

wc -l tests/sig-ui/vm_ip_filter_test.go

Repository: RedHatQE/openshift-virtualization-tests-design-docs

Length of output: 132


🏁 Script executed:

cat -n tests/sig-ui/vm_ip_filter_test.go | tail -50

Repository: RedHatQE/openshift-virtualization-tests-design-docs

Length of output: 2322


🏁 Script executed:

python3 << 'EOF'
# Check if the namespace is expected to exist
import re

with open('tests/sig-ui/vm_ip_filter_test.go', 'r') as f:
    content = f.read()
    
# Look for namespace creation or setup
setup_section = content[content.find('Context("Empty state'):content.find('It("[test_id:TS-CNV-72112-010]') + 100]
print("=== Context Setup ===")
print(setup_section)

# Check if there's any cleanup or deletion
cleanup = re.findall(r'DeferCleanup|BeforeEach|AfterEach', content)
print(f"\n=== Cleanup markers found: {cleanup}")
EOF

Repository: RedHatQE/openshift-virtualization-tests-design-docs

Length of output: 355


🏁 Script executed:

rg "\.List\(" tests/sig-ui/vm_ip_filter_test.go -B2 -A3

Repository: RedHatQE/openshift-virtualization-tests-design-docs

Length of output: 5291


Distinguish namespace-not-found errors from other API failures.

Lines 388-392 and 395-399 treat all errors identically by returning early, which masks real regressions (RBAC, transport, API failures) as passing empty-state tests. Every other .List() call in this file uses Expect(err).ToNot(HaveOccurred()).

Add the import:

apierrors "k8s.io/apimachinery/pkg/api/errors"

Then check for NotFound explicitly before returning:

vmiList, err := virtClient.VirtualMachineInstance(emptyNamespace).List(ctx, metav1.ListOptions{})
if err != nil {
	if apierrors.IsNotFound(err) {
		By("Namespace does not exist, which is equivalent to empty VM list")
		return
	}
	Expect(err).ToNot(HaveOccurred())
}

vmList, err := virtClient.VirtualMachine(emptyNamespace).List(ctx, metav1.ListOptions{})
if err != nil {
	if apierrors.IsNotFound(err) {
		By("Namespace does not exist for VM list, empty state applies")
		return
	}
	Expect(err).ToNot(HaveOccurred())
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/sig-ui/vm_ip_filter_test.go` around lines 389 - 399, The current error
handling for virtClient.VirtualMachineInstance(...).List and
virtClient.VirtualMachine(...).List treats every error as "namespace missing"
and returns, hiding real failures; add the import alias apierrors
"k8s.io/apimachinery/pkg/api/errors" and change each err check to explicitly
test apierrors.IsNotFound(err) and return only in that case, otherwise call
Expect(err).ToNot(HaveOccurred()): update the error branches around
VirtualMachineInstance(...).List and VirtualMachine(...).List to use
apierrors.IsNotFound(err) before returning so RBAC/transport/API errors surface
via Expect.

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.

3 participants