Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
13 changes: 13 additions & 0 deletions docs/reqstream/modeling/requirements.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -345,3 +345,16 @@ sections:
- lint
tests:
- Linter_Lint_WithMultipleCycles_ReportsAllCycles

- id: ReqStream-Lint-UnknownChildReference
title: >-
The requirements loader shall report an error when a requirement references a child
requirement ID that does not exist.
justification: |
References to non-existent child requirement IDs indicate typos or stale references.
Reporting these as errors ensures that requirement hierarchies remain consistent and
prevents silent failures in trace matrix generation.
tags:
- lint
tests:
- RequirementsLoader_Load_WithUnknownChildReference_ReportsError
9 changes: 9 additions & 0 deletions src/DemaConsulting.ReqStream/Modeling/RequirementsLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,19 @@
/// Known fields at the document root level.
/// </summary>
private static readonly HashSet<string> KnownDocumentFields =
new(StringComparer.Ordinal) { "sections", "mappings", "includes" };

Check warning on line 36 in src/DemaConsulting.ReqStream/Modeling/RequirementsLoader.cs

View workflow job for this annotation

GitHub Actions / Build / Build macos-latest

Define a constant instead of using this literal 'sections' 4 times.

Check warning on line 36 in src/DemaConsulting.ReqStream/Modeling/RequirementsLoader.cs

View workflow job for this annotation

GitHub Actions / Build / Build macos-latest

Define a constant instead of using this literal 'sections' 4 times.

Check warning on line 36 in src/DemaConsulting.ReqStream/Modeling/RequirementsLoader.cs

View workflow job for this annotation

GitHub Actions / Build / Build ubuntu-latest

Define a constant instead of using this literal 'sections' 4 times.

Check warning on line 36 in src/DemaConsulting.ReqStream/Modeling/RequirementsLoader.cs

View workflow job for this annotation

GitHub Actions / Build / Build windows-latest

Define a constant instead of using this literal 'sections' 4 times.

Check warning on line 36 in src/DemaConsulting.ReqStream/Modeling/RequirementsLoader.cs

View workflow job for this annotation

GitHub Actions / Build / Build windows-latest

Define a constant instead of using this literal 'sections' 4 times.

/// <summary>
/// Known fields within a section.
/// </summary>
private static readonly HashSet<string> KnownSectionFields =
new(StringComparer.Ordinal) { "title", "requirements", "sections" };

Check warning on line 42 in src/DemaConsulting.ReqStream/Modeling/RequirementsLoader.cs

View workflow job for this annotation

GitHub Actions / Build / Build macos-latest

Define a constant instead of using this literal 'title' 4 times.

Check warning on line 42 in src/DemaConsulting.ReqStream/Modeling/RequirementsLoader.cs

View workflow job for this annotation

GitHub Actions / Build / Build macos-latest

Define a constant instead of using this literal 'title' 4 times.

Check warning on line 42 in src/DemaConsulting.ReqStream/Modeling/RequirementsLoader.cs

View workflow job for this annotation

GitHub Actions / Build / Build ubuntu-latest

Define a constant instead of using this literal 'title' 4 times.

Check warning on line 42 in src/DemaConsulting.ReqStream/Modeling/RequirementsLoader.cs

View workflow job for this annotation

GitHub Actions / Build / Build windows-latest

Define a constant instead of using this literal 'title' 4 times.

Check warning on line 42 in src/DemaConsulting.ReqStream/Modeling/RequirementsLoader.cs

View workflow job for this annotation

GitHub Actions / Build / Build windows-latest

Define a constant instead of using this literal 'title' 4 times.

/// <summary>
/// Known fields within a requirement.
/// </summary>
private static readonly HashSet<string> KnownRequirementFields =
new(StringComparer.Ordinal) { "id", "title", "justification", "tests", "children", "tags" };

Check warning on line 48 in src/DemaConsulting.ReqStream/Modeling/RequirementsLoader.cs

View workflow job for this annotation

GitHub Actions / Build / Build macos-latest

Define a constant instead of using this literal 'tests' 4 times.

Check warning on line 48 in src/DemaConsulting.ReqStream/Modeling/RequirementsLoader.cs

View workflow job for this annotation

GitHub Actions / Build / Build ubuntu-latest

Define a constant instead of using this literal 'tests' 4 times.

Check warning on line 48 in src/DemaConsulting.ReqStream/Modeling/RequirementsLoader.cs

View workflow job for this annotation

GitHub Actions / Build / Build windows-latest

Define a constant instead of using this literal 'tests' 4 times.

/// <summary>
/// Known fields within a test mapping.
Expand Down Expand Up @@ -692,7 +692,7 @@
/// <param name="visiting">IDs currently on the active DFS stack.</param>
/// <param name="currentPath">Ordered list of IDs on the active DFS path (for cycle reporting).</param>
/// <param name="visited">IDs already fully processed.</param>
private static void ValidateCyclesFrom(

Check warning on line 695 in src/DemaConsulting.ReqStream/Modeling/RequirementsLoader.cs

View workflow job for this annotation

GitHub Actions / Build / Build macos-latest

Refactor this method to reduce its Cognitive Complexity from 16 to the 15 allowed.

Check warning on line 695 in src/DemaConsulting.ReqStream/Modeling/RequirementsLoader.cs

View workflow job for this annotation

GitHub Actions / Build / Build ubuntu-latest

Refactor this method to reduce its Cognitive Complexity from 16 to the 15 allowed.

Check warning on line 695 in src/DemaConsulting.ReqStream/Modeling/RequirementsLoader.cs

View workflow job for this annotation

GitHub Actions / Build / Build windows-latest

Refactor this method to reduce its Cognitive Complexity from 16 to the 15 allowed.
string reqId,
Dictionary<string, Requirement> allRequirements,
List<LintIssue> issues,
Expand All @@ -707,6 +707,15 @@
{
foreach (var childId in requirement.Children)
{
if (!allRequirements.ContainsKey(childId))
{
issues.Add(new LintIssue(
requirement.Location ?? reqId,
LintSeverity.Error,
$"Requirement '{reqId}' references unknown child '{childId}'"));
continue;
}
Comment thread
Malcolmnixon marked this conversation as resolved.

if (visiting.Contains(childId))
{
var cycleStart = currentPath.IndexOf(childId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ public void Requirements_Load_RequirementWithChildren_ParsesChildrenCorrectly()
children:
- ""AUTH-001""
- ""AUTH-002""
- id: ""AUTH-001""
title: ""The system shall validate user credentials.""
- id: ""AUTH-002""
title: ""The system shall reject invalid credentials.""
";
var filePath = Path.Combine(_testDirectory, "requirements.yaml");
File.WriteAllText(filePath, yamlContent);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -838,4 +838,26 @@ public void Linter_Lint_WithMultipleCycles_ReportsAllCycles()
.Count(line => line.Contains("Circular requirement reference detected"));
Assert.AreEqual(2, cycleCount, $"Expected exactly 2 cycle errors, got {cycleCount}: {errors}");
}

/// <summary>
/// Test that a child reference to a non-existent requirement ID is reported as an error.
/// </summary>
[TestMethod]
public void RequirementsLoader_Load_WithUnknownChildReference_ReportsError()
Comment thread
Malcolmnixon marked this conversation as resolved.
{
var reqFile = Path.Combine(_testDirectory, "unknown-child.yaml");
File.WriteAllText(reqFile, @"sections:
- title: Test Section
requirements:
- id: PARENT
title: Parent Requirement
children:
- NONEXISTENT
");

var (exitCode, errors) = RunLint(reqFile);

Assert.AreEqual(1, exitCode);
Assert.Contains("NONEXISTENT", errors);
Comment thread
Malcolmnixon marked this conversation as resolved.
Outdated
}
}
Loading