Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions dev/check-integration-bug-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Bug: `InfrahubCheckIntegrationItem.runtest` crashes with `AttributeError`

A pre-existing bug in the pytest plugin's `check-integration` test kind, discovered while
adding test coverage for the `pytest.Stash` migration in PR #985. Not introduced by that PR
— `git log` confirms the same defect existed before the migration.

## Symptom

Any YAML test using `kind: check-integration` fails at runtime with:

```text
AttributeError: 'InfrahubCheckIntegrationItem' object has no attribute 'check_instance'
```

The check never executes; the GraphQL query is never issued.

## Location

[infrahub_sdk/pytest_plugin/items/check.py:85-94](../infrahub_sdk/pytest_plugin/items/check.py#L85-L94)

```python
class InfrahubCheckIntegrationItem(InfrahubCheckItem):
def runtest(self) -> None:
input_data = self.session.stash[INFRAHUB_CLIENT_KEY].query_gql_query(
self.check_instance.query, # <-- AttributeError here
variables=self.test.spec.get_variables_data(),
)
passed = self.run_check(input_data)

if not passed and self.test.expect == InfrahubTestExpectedResult.PASS:
raise CheckResultError(name=self.name)
```

## Root cause

`self.check_instance` is declared as a type annotation in
[InfrahubCheckItem.__init__](../infrahub_sdk/pytest_plugin/items/check.py#L34) but never
assigned a value:

```python
class InfrahubCheckItem(InfrahubItem):
def __init__(self, ...) -> None:
super().__init__(...)
self.check_instance: InfrahubCheck # annotation only, no assignment
```

The attribute is only created later, inside
[instantiate_check](../infrahub_sdk/pytest_plugin/items/check.py#L36-L44):

```python
def instantiate_check(self) -> None:
...
self.check_instance = check_class()
```

`InfrahubCheckSmokeItem.runtest` calls `self.instantiate_check()` as its first line, and
`InfrahubCheckUnitProcessItem.runtest` calls it indirectly via `self.run_check()` —
both compensate. `InfrahubCheckIntegrationItem.runtest` is the only path that reads
`self.check_instance.query` *before* either call, so it always crashes.

`run_check` does call `instantiate_check`, but only after the failing line on 88.

## Why it has gone unnoticed

- No unit test covers `InfrahubCheckIntegrationItem.runtest`. Coverage of
[check.py](../infrahub_sdk/pytest_plugin/items/check.py) is 0% in
`tests/unit/pytest_plugin/`.
- Integration test kinds require a running Infrahub instance, so users hit a real
GraphQL endpoint to exercise this — the AttributeError surfaces before any HTTP call,
but the failure mode is easy to misdiagnose as "my test setup is wrong."
- Smoke and unit-process kinds for the same resource work fine, masking the issue.

## Suggested fix

Call `instantiate_check()` first, mirroring `InfrahubCheckSmokeItem.runtest`:

```python
class InfrahubCheckIntegrationItem(InfrahubCheckItem):
def runtest(self) -> None:
self.instantiate_check()
input_data = self.session.stash[INFRAHUB_CLIENT_KEY].query_gql_query(
self.check_instance.query,
variables=self.test.spec.get_variables_data(),
)
passed = self.run_check(input_data)

if not passed and self.test.expect == InfrahubTestExpectedResult.PASS:
raise CheckResultError(name=self.name)
```

`run_check` will call `instantiate_check` a second time, which is wasted work but not
incorrect — it just re-imports the check class and rebuilds the instance. If that matters,
also drop the redundant call inside `run_check` and require all callers to instantiate
explicitly. The other items (`InfrahubPythonTransformIntegrationItem`) already follow that
pattern: they call `self.instantiate_transform()` at the top of `runtest`.

## Reproducing

A draft `test_check_integration` was written and removed from
[tests/unit/pytest_plugin/test_plugin.py](../tests/unit/pytest_plugin/test_plugin.py)
because there is no way to make it pass without the fix above. The reproduction shape is:

1. Create a YAML test with `kind: check-integration` and a `variables: {}` spec.
2. Create an `infrahub_config.yml` that points to a check class file.
3. Create a check class with `query = "..."` and a `validate(data)` method.
4. Run `pytest --infrahub-repo-config=infrahub_config.yml`.

Expected: 1 passed. Actual: 1 failed with the AttributeError above.

Once the fix lands, the test can be added back; the test scaffolding is straightforward
because `httpx_mock` from the outer test scope intercepts the inner `pytester`-driven
client (pytester runs in-process by default).

## Scope

This bug is out of scope for PR #985 (stash migration is a pure refactor). It should be
fixed in a follow-up PR along with the test that covers the path.
Loading