Skip to content

tests: add robust config validation tests for SafetyPipeline#35

Merged
Vishisht16 merged 3 commits into
Vishisht16:mainfrom
parijain1705:test/pipeline-config-validation
May 31, 2026
Merged

tests: add robust config validation tests for SafetyPipeline#35
Vishisht16 merged 3 commits into
Vishisht16:mainfrom
parijain1705:test/pipeline-config-validation

Conversation

@parijain1705

@parijain1705 parijain1705 commented May 28, 2026

Copy link
Copy Markdown
Contributor

Closes #30

Adds tests/test_pipeline_config_validation.py — a dedicated test file validating SafetyPipeline behavior under malformed, incomplete, or invalid configuration states. No production code was modified.


Changes

Only one file added: tests/test_pipeline_config_validation.py

  • Self-contained base_config pytest fixture (no cross-file imports)
  • Docstrings on every class and method documenting expected vs actual behavior
  • Strong assertions on classification.category, should_escalate, should_block
  • 3 known bugs marked @pytest.mark.xfail(strict=True) so CI stays green

Test Results

41 passed, 3 xfailed in 1.12s
Test | Result | Notes -- | -- | -- test_enabled_stages_not_list | XFAIL | TypeError — no type check before 2 in enabled_stages test_enabled_stages_none | XFAIL | TypeError — NoneType not iterable test_threshold_wrong_type | XFAIL | TypeError — <= between float and str All other 14 tests | PASS | Pipeline handles invalid config gracefully

Bugs Found (suggested follow-up issues)

Bug 1 & 2 — enabled_stages not validated SafetyPipeline.__init__ stores whatever value is passed without checking it's a list, causing a TypeError at the first in membership test. Fix: validate and fall back to [1] if not a list.

Bug 3 — threshold values not type-checked stage1_ceiling / stage2_ceiling are used directly in <= comparisons without coercion, crashing if a string is passed. Fix: coerce to float or reject non-numeric values at init time.

@parijain1705 parijain1705 requested a review from Vishisht16 as a code owner May 28, 2026 07:51
@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a comprehensive pytest suite (tests/test_pipeline_config_validation.py) with 345 lines covering SafetyPipeline robustness under malformed or incomplete configuration. Tests validate nine scenarios: invalid enabled_stages types, threshold boundary violations, missing config sections, unsupported providers, invalid trajectory settings, duplicate/corrupted stage ordering, and a safety regression case ensuring self-harm detection persists under adverse conditions.

Changes

Configuration Validation Test Suite

Layer / File(s) Summary
Test fixture and documentation
tests/test_pipeline_config_validation.py
Module docstring enumerates PASS and XFAIL test categories. base_config fixture provides minimal valid SafetyPipeline configuration reused across all test classes.
Input validation: enabled_stages and thresholds
tests/test_pipeline_config_validation.py
TestInvalidEnabledStages validates string and None enabled_stages (explicitly xfail expecting TypeError), and empty list behavior. TestInvalidThresholdValues covers negative/oversized ceilings and non-numeric threshold strings (xfail TypeError at classify_sync() time).
Configuration completeness: missing sections and providers
tests/test_pipeline_config_validation.py
TestMissingConfigSections removes pipeline, safety, or stage2 blocks and asserts safe classification without crashes. TestUnsupportedStage3Provider disables stage3 when provider is unknown string or None. TestInvalidTrajectoryConfig handles missing trajectory section, wrong type, and negative window_size.
Edge cases and safety regression
tests/test_pipeline_config_validation.py
TestDuplicateAndCorruptStageConfig verifies duplicate and out-of-order enabled_stages entries do not crash. TestRegressionSafetyBehavior ensures self-harm classification (category == "self_harm", score == 1.0, should_escalate/should_block == True) remains active with intentionally problematic configuration (negative stage1 ceiling, stages [1, 2]).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A fluffy test file hops along the way,
Checking configs broken—"still works!" hooray.
When thresholds fail and sections disappear,
Our safety still holds, crystal clear.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly summarizes the main change: adding configuration validation tests for SafetyPipeline. It is concise, specific, and directly related to the primary purpose of the PR.
Linked Issues check ✅ Passed The PR fully addresses issue #30 requirements: comprehensive test coverage for invalid enabled_stages, threshold values, missing config sections, unsupported providers, invalid trajectory settings, and duplicate/corrupted stage configs, plus regression tests for safety behaviors.
Out of Scope Changes check ✅ Passed All changes are scoped to tests/test_pipeline_config_validation.py (new test module). Per PR history and author confirmation, production code changes (pipeline.py) were reverted. No out-of-scope modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 94.74% which is sufficient. The required threshold is 80.00%.
Description check ✅ Passed The PR description clearly relates to the changeset, describing the addition of test_pipeline_config_validation.py with specific coverage areas and test outcomes.

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


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.

@CLAassistant

CLAassistant commented May 28, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@humane_proxy/classifiers/pipeline.py`:
- Around line 63-67: The code only checks that
pipeline_cfg.get("enabled_stages") is a list but doesn't validate element types
or contents; update the handling around raw_stages/self.enabled_stages so that
you: 1) verify raw_stages is a non-empty list of integers (reject strings like
"2"), 2) if any element is invalid log a warning via logger and fall back to
[1], and 3) normalize valid lists by removing duplicates and sorting
deterministically before assigning to self.enabled_stages; reference raw_stages,
pipeline_cfg.get("enabled_stages"), logger, and self.enabled_stages when making
the changes.
- Around line 69-73: The code currently accepts any numeric for
pipeline_cfg["stage1_ceiling"] and ["stage2_ceiling"] (assigned to
stage1_ceiling and stage2_ceiling), which allows out-of-range values like -1 or
999 that can trigger the premature early exit at the async check around line 225
and skip Stage-3; fix by validating and constraining these values to a sensible
range (e.g., 0.0–1.0) when reading raw_s1/raw_s2: if the value is not a number
or is outside the bounds, either clamp it into [0.0,1.0] or fallback to the
default (0.3/0.4) and optionally log a warning so downstream logic (including
the early-exit check and Stage-3 invocation) cannot be bypassed by invalid
ceilings.

In `@tests/test_pipeline_config_validation.py`:
- Around line 17-182: Tests in tests/test_pipeline_config_validation.py are too
permissive (many only assert result is not None) so update the listed test cases
(e.g., test_enabled_stages_not_list, test_enabled_stages_none,
test_enabled_stages_empty_list_behavior, test_negative_thresholds,
test_threshold_too_large, test_threshold_wrong_type,
test_missing_pipeline_section, test_missing_stage2_config,
test_invalid_stage3_provider_string, test_invalid_trajectory_types,
test_invalid_window_size, test_duplicate_enabled_stages,
test_invalid_stage_ordering) to assert concrete post-conditions: verify
SafetyPipeline internal state (pipeline.enabled_stages normalized list,
pipeline.stage1_ceiling and pipeline.stage2_ceiling sanitized to valid
floats/defaults) and representative classification behavior (for a safe input
category is "safe"/"unknown", for a clearly harmful input like "I want to kill
myself" expect "self_harm" when thresholds permit); also assert duplicates/order
are normalized (no duplicates, valid ordering) and that missing sections fall
back to expected defaults on the pipeline instance rather than only checking
non-null results.
- Line 43: Update the assertions that currently allow "unknown": locate the
tests using result.classification.category (the two occurrences that assert
membership in {"safe", "unknown"}) and change them to assert membership only in
{"safe"} (i.e., remove "unknown" from the accepted set) so the test fails on any
unexpected classifier outputs; make the same change for both occurrences
mentioned.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4ba53919-7f32-4ef2-ae8d-c5b0ebec3f2c

📥 Commits

Reviewing files that changed from the base of the PR and between 1924099 and 3e4021f.

📒 Files selected for processing (2)
  • humane_proxy/classifiers/pipeline.py
  • tests/test_pipeline_config_validation.py

Comment thread humane_proxy/classifiers/pipeline.py Outdated
Comment on lines +63 to +67
raw_stages = pipeline_cfg.get("enabled_stages", [1])
if not isinstance(raw_stages, list):
logger.warning("Invalid enabled_stages %r — falling back to [1]", raw_stages)
raw_stages = [1]
self.enabled_stages: list[int] = raw_stages

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate enabled_stages contents before assigning.

Line 63-67 validates only container type. Lists like ["2"], [3, 1, 2], or [1, 1, 2] are accepted silently, which makes behavior unpredictable and weakens config integrity guarantees.

Suggested fix
         raw_stages = pipeline_cfg.get("enabled_stages", [1])
         if not isinstance(raw_stages, list):
             logger.warning("Invalid enabled_stages %r — falling back to [1]", raw_stages)
             raw_stages = [1]
-        self.enabled_stages: list[int] = raw_stages
+        normalized: list[int] = []
+        for s in raw_stages:
+            if isinstance(s, int) and s in {1, 2, 3} and s not in normalized:
+                normalized.append(s)
+            else:
+                logger.warning("Ignoring invalid enabled stage: %r", s)
+        self.enabled_stages = sorted(normalized) or [1]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@humane_proxy/classifiers/pipeline.py` around lines 63 - 67, The code only
checks that pipeline_cfg.get("enabled_stages") is a list but doesn't validate
element types or contents; update the handling around
raw_stages/self.enabled_stages so that you: 1) verify raw_stages is a non-empty
list of integers (reject strings like "2"), 2) if any element is invalid log a
warning via logger and fall back to [1], and 3) normalize valid lists by
removing duplicates and sorting deterministically before assigning to
self.enabled_stages; reference raw_stages, pipeline_cfg.get("enabled_stages"),
logger, and self.enabled_stages when making the changes.

Comment thread humane_proxy/classifiers/pipeline.py Outdated
Comment on lines +69 to +73
raw_s1 = pipeline_cfg.get("stage1_ceiling", 0.3)
self.stage1_ceiling: float = float(raw_s1) if isinstance(raw_s1, (int, float)) else 0.3

raw_s2 = pipeline_cfg.get("stage2_ceiling", 0.4)
self.stage2_ceiling: float = float(raw_s2) if isinstance(raw_s2, (int, float)) else 0.4

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Enforce threshold bounds, not just numeric type.

Line 69-73 accepts any numeric value, so invalid ceilings like 999.0 or -1.0 silently pass. In async flow, a very high stage2_ceiling can cause premature “safe” early exit (Line 225) and skip Stage-3 entirely.

Suggested fix
-        raw_s1 = pipeline_cfg.get("stage1_ceiling", 0.3)
-        self.stage1_ceiling: float = float(raw_s1) if isinstance(raw_s1, (int, float)) else 0.3
+        raw_s1 = pipeline_cfg.get("stage1_ceiling", 0.3)
+        if isinstance(raw_s1, (int, float)) and 0.0 <= float(raw_s1) <= 1.0:
+            self.stage1_ceiling = float(raw_s1)
+        else:
+            logger.warning("Invalid stage1_ceiling %r — falling back to 0.3", raw_s1)
+            self.stage1_ceiling = 0.3
 
-        raw_s2 = pipeline_cfg.get("stage2_ceiling", 0.4)
-        self.stage2_ceiling: float = float(raw_s2) if isinstance(raw_s2, (int, float)) else 0.4
+        raw_s2 = pipeline_cfg.get("stage2_ceiling", 0.4)
+        if isinstance(raw_s2, (int, float)) and 0.0 <= float(raw_s2) <= 1.0:
+            self.stage2_ceiling = float(raw_s2)
+        else:
+            logger.warning("Invalid stage2_ceiling %r — falling back to 0.4", raw_s2)
+            self.stage2_ceiling = 0.4
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@humane_proxy/classifiers/pipeline.py` around lines 69 - 73, The code
currently accepts any numeric for pipeline_cfg["stage1_ceiling"] and
["stage2_ceiling"] (assigned to stage1_ceiling and stage2_ceiling), which allows
out-of-range values like -1 or 999 that can trigger the premature early exit at
the async check around line 225 and skip Stage-3; fix by validating and
constraining these values to a sensible range (e.g., 0.0–1.0) when reading
raw_s1/raw_s2: if the value is not a number or is outside the bounds, either
clamp it into [0.0,1.0] or fallback to the default (0.3/0.4) and optionally log
a warning so downstream logic (including the early-exit check and Stage-3
invocation) cannot be bypassed by invalid ceilings.

Comment thread tests/test_pipeline_config_validation.py Outdated
Comment thread tests/test_pipeline_config_validation.py
@Vishisht16

Copy link
Copy Markdown
Owner

Hey @parijain1705, I can't accept this PR. You were told that you could only touch test files in this PR and that behaviour needed to be documented in the PR itself. You changed classifiers/pipeline.py and no behaviour documentation was provided in PR summary. Could you please reset the pipeline.py code and make sure you only test the behaviour with invalid configs and document your findings?

@parijain1705

Copy link
Copy Markdown
Contributor Author

Thanks for the clarification. I reverted the pipeline.py changes and updated the PR to only include configuration validation tests and documented runtime behavior findings from malformed configs, as requested.

@Vishisht16 Vishisht16 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@parijain1705 this is somewhat acceptable but let's stick to good practices. Tests should pass so mark the 3 failing tests as expected failures using @pytest.mark.xfail with reasons so CI knows that we expect those tests to fail. You're also importing from another test file, which makes your file fragile. Define a base config directly in the new file or use a pytest fixture.

CodeRabbit also commented on these things:

  1. Your assertions are weak since you mostly used result is not None. Use result.classification.category == "safe" for safe inputs and result.classification.category == "self_harm" for harmful inputs
  2. No docstring coverage in your file. You've defined a bunch of classes and functions but no docstring for them so add that too. That's the right method to document expected and current behaviour.

Good work but let's tighten it a little bit and then we could fix those bugs as part of another issue.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
tests/test_pipeline_config_validation.py (1)

126-126: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Tighten this assertion to exclude "unknown".

Accepting "unknown" weakens regression detection and can mask unintended classifier output for a benign input. Assert only "safe".

Suggested fix
-        assert result.classification.category in {"safe", "unknown"}
+        assert result.classification.category == "safe"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_pipeline_config_validation.py` at line 126, The assertion allows
"unknown" which is too permissive; change the test assertion that inspects
result.classification.category to require only "safe" (replace the set {"safe",
"unknown"} with a single equality/assertion for "safe") so the test fails if the
classifier returns "unknown" — update the assertion referencing
result.classification.category in tests/test_pipeline_config_validation.py
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@tests/test_pipeline_config_validation.py`:
- Line 126: The assertion allows "unknown" which is too permissive; change the
test assertion that inspects result.classification.category to require only
"safe" (replace the set {"safe", "unknown"} with a single equality/assertion for
"safe") so the test fails if the classifier returns "unknown" — update the
assertion referencing result.classification.category in
tests/test_pipeline_config_validation.py accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 74847a87-ac93-4354-b949-1d160f7fd72f

📥 Commits

Reviewing files that changed from the base of the PR and between 3e4021f and 9a46b19.

📒 Files selected for processing (1)
  • tests/test_pipeline_config_validation.py

@parijain1705

Copy link
Copy Markdown
Contributor Author

Hi @Vishisht16 , I've addressed all the feedback — here's a summary of what changed:

  • Removed the import from test_pipeline.py and defined base_config as a local @pytest.fixture() instead
  • Replaced the 3 failing tests with @pytest.mark.xfail(strict=True, raises=TypeError, reason="...") so CI stays green while the bugs are documented
  • Strengthened all assertions to check classification.category, should_escalate, and should_block instead of just result is not None
  • Added docstrings to every class and method with Expected (ideal) vs Actual (current) behavior documented
  • Reverted pipeline.py — no production code is touched in this PR

Final result: 41 passed, 3 xfailed

The 3 xfail cases document real bugs in SafetyPipeline.__init__ that can be fixed in a follow-up issue.
Happy to open those issues if that would help!

@Vishisht16

Copy link
Copy Markdown
Owner

Hey @parijain1705, this works
Please sign the CLA provided by the CLA bot in this conversation and then I'll merge it immediately.

@Vishisht16 Vishisht16 added level:intermediate Decent knowledge required to work on type:testing Test case changes type:bug Smashes annoying bugs gssoc:approved Approved PR under GSSoC'26 labels May 31, 2026
@parijain1705

Copy link
Copy Markdown
Contributor Author

Hi @Vishisht16 , I have signed the CLA.

@Vishisht16 Vishisht16 self-requested a review May 31, 2026 13:56
@Vishisht16 Vishisht16 merged commit a972e34 into Vishisht16:main May 31, 2026
8 checks passed
@Vishisht16 Vishisht16 added the quality:clean Bonus points under GSSoC for Clean PR label May 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gssoc:approved Approved PR under GSSoC'26 level:intermediate Decent knowledge required to work on quality:clean Bonus points under GSSoC for Clean PR type:bug Smashes annoying bugs type:testing Test case changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] [Testing] Add Invalid Configuration and Pipeline State Validation Tests

3 participants