Skip to content

test: impact analysis DAG traversal and training import markdown parser#374

Open
anandgupta42 wants to merge 1 commit intomainfrom
claude/test-impact-training-session_011NpWhe2sbo52QP8Af9dA34
Open

test: impact analysis DAG traversal and training import markdown parser#374
anandgupta42 wants to merge 1 commit intomainfrom
claude/test-impact-training-session_011NpWhe2sbo52QP8Af9dA34

Conversation

@anandgupta42
Copy link
Contributor

@anandgupta42 anandgupta42 commented Mar 22, 2026

What does this PR do?

Adds 34 unit tests for two newly-added tools (impact-analysis, training-import) that shipped in commit edd58cd with zero test coverage. Both tools contain non-trivial pure logic (graph traversal, markdown parsing) that is critical to user-facing output.

1. findDownstream + formatImpactReportsrc/altimate/tools/impact-analysis.ts (15 new tests)

The impact_analysis tool helps users understand the blast radius of dbt model/column changes before making them. The core findDownstream function performs DAG traversal across dbt manifests, and formatImpactReport generates the user-facing impact report. Zero tests existed. New coverage includes:

  • Leaf model with no dependents (base case)
  • Direct dependents at depth 1 with qualified name parsing (project.stg_ordersstg_orders)
  • Transitive dependents across 3+ depths with correct depth tracking
  • Dependency path tracking (used in "via:" display strings)
  • Diamond DAG deduplication (visited set prevents duplicates)
  • Self-referencing model behavior documentation (edge case — not a valid dbt graph)
  • Materialization metadata preservation (view, table, incremental)
  • Nonexistent model returns empty
  • Report formatting: safe (0 downstream), BREAKING remove warning, rename warning, retype caution
  • Column-level impact with affected output columns display
  • Percentage calculation edge case (0 total models → no NaN/Infinity)
  • Transitive dependency path rendering with materialization annotations

2. parseMarkdownSections + slugifysrc/altimate/tools/training-import.ts (19 new tests)

The training_import tool enables bulk-loading team standards from markdown documents into the training system. parseMarkdownSections is a state-machine parser that splits markdown by H2 headings and propagates H1 context. slugify normalizes heading text into storage-safe keys. Zero tests existed. New coverage includes:

  • Simple H2 section parsing with name and content extraction
  • H1 context prepending ("Context: SQL Style Guide" prefix)
  • H1 context updating when new H1 appears (state machine transition)
  • No H2 headings → empty result (H3 doesn't trigger section creation)
  • Empty string → empty result
  • Empty H2 sections (H2 immediately followed by another H2) are skipped
  • H3 lines preserved as content within H2 sections
  • Last section captured (post-loop flush correctness)
  • H2 names are slugified (including dots in version headings: v2.1v21)
  • Content trimming (no leading/trailing blank lines)
  • Multiple H1s without H2s produce no sections
  • Slugify: lowercase + hyphens, special character removal, space collapsing, realistic leading/trailing hyphen trimming, 64-char truncation, empty/special-only inputs, unicode stripping (café → caf, naïve → nave)

Note: Both findDownstream/formatImpactReport and parseMarkdownSections/slugify are module-private (not exported). Tests use the established function-copy pattern (same as test/memory/prompt.test.ts) to test the pure logic in isolation.

Type of change

  • New feature (non-breaking change which adds functionality)

Issue for this PR

N/A — proactive test coverage for newly shipped tools

How did you verify your code works?

bun test test/altimate/tools/impact-analysis.test.ts   # 15 pass, 42 expect() calls
bun test test/altimate/tools/training-import.test.ts    # 19 pass, 37 expect() calls

All tests were reviewed by a critic agent and revised based on feedback (self-reference test renamed to behavior-documentation-only, percentage edge case uses NaN/Infinity guards, slugify tests use realistic inputs, unicode test covers multi-byte characters).

Checklist

  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

https://claude.ai/code/session_011NpWhe2sbo52QP8Af9dA34

Summary by CodeRabbit

  • Tests
    • Added comprehensive test coverage for impact analysis and training import utilities, improving verification of internal systems and edge case handling.

Add 34 tests for two newly-added tools (impact-analysis, training-import)
that shipped with zero test coverage. These tests cover the core pure logic:
findDownstream graph traversal, formatImpactReport output, parseMarkdownSections
parsing, and slugify normalization.

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

https://claude.ai/code/session_011NpWhe2sbo52QP8Af9dA34
Copy link

@claude claude bot left a comment

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review.

@coderabbitai
Copy link

coderabbitai bot commented Mar 22, 2026

📝 Walkthrough

Walkthrough

Adds two new Bun test files: one for impact analysis functionality testing DAG traversal and report generation, another for training import utilities testing slug generation and markdown section parsing with comprehensive unit test coverage.

Changes

Cohort / File(s) Summary
Impact Analysis Testing
packages/opencode/test/altimate/tools/impact-analysis.test.ts
New test file with in-test implementations of findDownstream (DAG traversal with deduplication and depth tracking) and formatImpactReport (impact report generation). Comprehensive test coverage for empty/leaf models, transitive dependencies, qualified-name parsing, materialized metadata preservation, diamond dependency handling, and various report scenarios.
Training Import Testing
packages/opencode/test/altimate/tools/training-import.test.ts
New test file with in-test implementations of slugify (text normalization with lowercasing, hyphenation, character removal, truncation, and trimming) and parseMarkdownSections (H2 section parsing with context handling from H1 headings). Unit tests cover edge cases, whitespace handling, section detection, and content assembly.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • #350: These new tests directly verify the implementations of ImpactAnalysisTool (findDownstream, formatImpactReport) and TrainingImportTool (slugify, parseMarkdownSections) introduced in that PR.

Suggested labels

contributor

Poem

🐰 A rabbit hops through test files clear,
Checking traversals far and near,
Slugs and sections, all in place,
No bugs can hide from this trace!
With comprehensive coverage, we celebrate,
Quality assurance—truly great! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary changes: adding tests for impact analysis DAG traversal and training import markdown parser functionality.
Description check ✅ Passed The description comprehensively covers what changed, why it was needed, test plan verification, and includes a filled checklist, exceeding template requirements.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/test-impact-training-session_011NpWhe2sbo52QP8Af9dA34

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: 2

🧹 Nitpick comments (1)
packages/opencode/test/altimate/tools/impact-analysis.test.ts (1)

160-183: Depth/path assertions are currently traversal-order brittle.

At Line 169-Line 172 and Line 181-Line 182, assertions rely on array index order. A harmless traversal-order refactor could fail these tests even with correct graph results.

Make assertions order-independent
-    expect(result[0]).toMatchObject({ name: "fct_orders", depth: 1 })
-    expect(result[1]).toMatchObject({ name: "dim_orders", depth: 2 })
-    expect(result[2]).toMatchObject({ name: "report_orders", depth: 3 })
+    expect(result).toEqual(
+      expect.arrayContaining([
+        expect.objectContaining({ name: "fct_orders", depth: 1 }),
+        expect.objectContaining({ name: "dim_orders", depth: 2 }),
+        expect.objectContaining({ name: "report_orders", depth: 3 }),
+      ]),
+    )

-    expect(result[0].path).toEqual(["stg_orders", "fct_orders"])
-    expect(result[1].path).toEqual(["stg_orders", "fct_orders", "report"])
+    const byName = new Map(result.map((r) => [r.name, r]))
+    expect(byName.get("fct_orders")?.path).toEqual(["stg_orders", "fct_orders"])
+    expect(byName.get("report")?.path).toEqual(["stg_orders", "fct_orders", "report"])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/test/altimate/tools/impact-analysis.test.ts` around lines
160 - 183, Tests assert specific positions in the findDownstream result array
which makes them brittle to traversal-order changes; update the two tests using
result so they assert order-independently (e.g., verify length and that the
array contains objects with the expected name/depth/path values using
arrayContaining or by locating items by name before checking depth/path) while
keeping references to findDownstream, result, and the specific expected entries
(fct_orders, dim_orders, report_orders, and their paths) so the assertions
validate content regardless of traversal order.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/test/altimate/tools/impact-analysis.test.ts`:
- Around line 3-136: The test is using copied implementations of findDownstream
and formatImpactReport which can drift from production; modify the production
module to export a test-only hook object (e.g., add export const __test__ = {
findDownstream, formatImpactReport }) and update the test to import these
functions from that module instead of keeping the duplicated implementations in
the test file; then remove the duplicated findDownstream and formatImpactReport
code from the test so tests exercise the real production implementations.

In `@packages/opencode/test/altimate/tools/training-import.test.ts`:
- Around line 91-94: The test asserts that slugify truncates to 64 chars but
production slugify (in datamate.ts) doesn't; either remove the test in
training-import.test.ts or modify the slugify function to enforce
truncation—update datamate.ts's slugify to return the normalized slug.slice(0,
64) (and keep the test), or if truncation is not desired, delete the "truncates
to 64 characters" test; locate symbols slugify and datamate.ts to apply the
change and ensure tests reflect the chosen behavior.

---

Nitpick comments:
In `@packages/opencode/test/altimate/tools/impact-analysis.test.ts`:
- Around line 160-183: Tests assert specific positions in the findDownstream
result array which makes them brittle to traversal-order changes; update the two
tests using result so they assert order-independently (e.g., verify length and
that the array contains objects with the expected name/depth/path values using
arrayContaining or by locating items by name before checking depth/path) while
keeping references to findDownstream, result, and the specific expected entries
(fct_orders, dim_orders, report_orders, and their paths) so the assertions
validate content regardless of traversal order.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3de7d166-2876-43ce-9927-ca7c7d14bd91

📥 Commits

Reviewing files that changed from the base of the PR and between d10303c and cabfe5f.

📒 Files selected for processing (2)
  • packages/opencode/test/altimate/tools/impact-analysis.test.ts
  • packages/opencode/test/altimate/tools/training-import.test.ts

Comment on lines +3 to +136
// Copy of findDownstream and formatImpactReport from
// src/altimate/tools/impact-analysis.ts (not exported, tested standalone)

interface DownstreamModel {
name: string
depth: number
materialized?: string
path: string[]
}

function findDownstream(
targetName: string,
models: Array<{ name: string; depends_on: string[]; materialized?: string }>,
): DownstreamModel[] {
const results: DownstreamModel[] = []
const visited = new Set<string>()

function walk(name: string, depth: number, path: string[]) {
for (const model of models) {
if (visited.has(model.name)) continue
const deps = model.depends_on.map((d) => d.split(".").pop())
if (deps.includes(name)) {
visited.add(model.name)
const newPath = [...path, model.name]
results.push({
name: model.name,
depth,
materialized: model.materialized,
path: newPath,
})
walk(model.name, depth + 1, newPath)
}
}
}

walk(targetName, 1, [targetName])
return results
}

function formatImpactReport(data: {
model: string
column?: string
changeType: string
direct: DownstreamModel[]
transitive: DownstreamModel[]
affectedTestCount: number
columnImpact: string[]
totalModels: number
}): string {
const lines: string[] = []
const target = data.column ? `${data.model}.${data.column}` : data.model
lines.push(`Impact Analysis: ${data.changeType.toUpperCase()} ${target}`)
lines.push("".padEnd(60, "="))

const totalAffected = data.direct.length + data.transitive.length
const pct = data.totalModels > 0 ? ((totalAffected / data.totalModels) * 100).toFixed(1) : "0"
lines.push(`Blast radius: ${totalAffected}/${data.totalModels} models (${pct}%)`)
lines.push("")

if (data.changeType === "remove" && totalAffected > 0) {
lines.push("WARNING: This is a BREAKING change. All downstream models will fail.")
lines.push("")
} else if (data.changeType === "rename" && totalAffected > 0) {
lines.push("WARNING: Rename requires updating all downstream references.")
lines.push("")
} else if (data.changeType === "retype" && totalAffected > 0) {
lines.push("CAUTION: Type change may cause implicit casts or failures in downstream models.")
lines.push("")
}

if (data.direct.length > 0) {
lines.push(`Direct Dependents (${data.direct.length})`)
lines.push("".padEnd(40, "-"))
for (const d of data.direct) {
const mat = d.materialized ? ` [${d.materialized}]` : ""
lines.push(` ${d.name}${mat}`)
}
lines.push("")
}

if (data.transitive.length > 0) {
lines.push(`Transitive Dependents (${data.transitive.length})`)
lines.push("".padEnd(40, "-"))
for (const d of data.transitive) {
const mat = d.materialized ? ` [${d.materialized}]` : ""
const path = d.path.join(" \u2192 ")
lines.push(` ${d.name}${mat} (via: ${path})`)
}
lines.push("")
}

if (data.column && data.columnImpact.length > 0) {
lines.push(`Affected Output Columns (${data.columnImpact.length})`)
lines.push("".padEnd(40, "-"))
for (const col of data.columnImpact) {
lines.push(` ${col}`)
}
lines.push("")
}

if (data.affectedTestCount > 0) {
lines.push(`Tests in project: ${data.affectedTestCount}`)
lines.push("".padEnd(40, "-"))
lines.push(` Run \`dbt test\` to verify all ${data.affectedTestCount} tests still pass after this change.`)
lines.push("")
}

if (totalAffected === 0) {
lines.push("No downstream models depend on this. Change is safe to make.")
}

if (totalAffected > 0) {
lines.push("Recommended Actions")
lines.push("".padEnd(40, "-"))
if (data.changeType === "remove") {
lines.push("1. Update all downstream models to remove references")
lines.push("2. Run `dbt test` to verify no broken references")
lines.push("3. Consider deprecation period before removal")
} else if (data.changeType === "rename") {
lines.push("1. Update all downstream SQL references to new name")
lines.push("2. Run `dbt compile` to verify all models compile")
lines.push("3. Run `dbt test` to verify correctness")
} else if (data.changeType === "retype") {
lines.push("1. Check downstream models for implicit type casts")
lines.push("2. Verify aggregations and joins still work correctly")
lines.push("3. Run `dbt test` with data validation")
} else {
lines.push("1. Review downstream models for compatibility")
lines.push("2. Run `dbt compile` and `dbt test`")
}
}

return lines.join("\n")
}
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

Test-local function copies can drift from production behavior.

At Line 3-Line 5, this file explicitly tests copied implementations rather than the real functions in src/altimate/tools/impact-analysis.ts. That can let source regressions pass unnoticed if this copy is not updated in lockstep.

Suggested direction
-// Copy of findDownstream and formatImpactReport from
-// src/altimate/tools/impact-analysis.ts (not exported, tested standalone)
-// ...local implementations...
+// Import test hooks from source to ensure tests execute production logic.
+import { __test__ } from "../../../src/altimate/tools/impact-analysis"
+const { findDownstream, formatImpactReport } = __test__

And in src/altimate/tools/impact-analysis.ts:

// Keep internal APIs private in app code, but expose test hooks intentionally.
export const __test__ = {
  findDownstream,
  formatImpactReport,
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/test/altimate/tools/impact-analysis.test.ts` around lines 3
- 136, The test is using copied implementations of findDownstream and
formatImpactReport which can drift from production; modify the production module
to export a test-only hook object (e.g., add export const __test__ = {
findDownstream, formatImpactReport }) and update the test to import these
functions from that module instead of keeping the duplicated implementations in
the test file; then remove the duplicated findDownstream and formatImpactReport
code from the test so tests exercise the real production implementations.

Comment on lines +91 to +94
test("truncates to 64 characters", () => {
const long = "a".repeat(100)
expect(slugify(long).length).toBe(64)
})
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

Truncation test validates behavior that doesn't exist in production.

This test asserts 64-character truncation, but the production slugify in datamate.ts does not include .slice(0, 64). This test passes against the local copy but does not reflect actual production behavior.

If truncation is intentionally absent from production, remove this test. If truncation is required, update the production code.

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

In `@packages/opencode/test/altimate/tools/training-import.test.ts` around lines
91 - 94, The test asserts that slugify truncates to 64 chars but production
slugify (in datamate.ts) doesn't; either remove the test in
training-import.test.ts or modify the slugify function to enforce
truncation—update datamate.ts's slugify to return the normalized slug.slice(0,
64) (and keep the test), or if truncation is not desired, delete the "truncates
to 64 characters" test; locate symbols slugify and datamate.ts to apply the
change and ensure tests reflect the chosen behavior.

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.

1 participant