diff --git a/packages/opencode/src/altimate/native/dbt/manifest.ts b/packages/opencode/src/altimate/native/dbt/manifest.ts index bd70a116ea..3680ae3f4a 100644 --- a/packages/opencode/src/altimate/native/dbt/manifest.ts +++ b/packages/opencode/src/altimate/native/dbt/manifest.ts @@ -10,6 +10,7 @@ import type { DbtManifestResult, DbtModelInfo, DbtSourceInfo, + DbtTestInfo, ModelColumn, } from "../types" @@ -30,6 +31,7 @@ export async function parseManifest(params: DbtManifestParams): Promise Promise) | null = null + +/** Clear all registered handlers and lazy registration hook (for test isolation). */ export function reset(): void { nativeHandlers.clear() + _ensureRegistered = null } -/** Lazy registration hook — set by native/index.ts */ -let _ensureRegistered: (() => Promise) | null = null - /** Called by native/index.ts to set the lazy registration function. */ export function setRegistrationHook(fn: () => Promise): void { _ensureRegistered = fn diff --git a/packages/opencode/src/altimate/native/types.ts b/packages/opencode/src/altimate/native/types.ts index 8d0f3978fc..f4ed9d95df 100644 --- a/packages/opencode/src/altimate/native/types.ts +++ b/packages/opencode/src/altimate/native/types.ts @@ -188,9 +188,16 @@ export interface DbtSourceInfo { columns: ModelColumn[] } +export interface DbtTestInfo { + unique_id: string + name: string + depends_on: string[] +} + export interface DbtManifestResult { models: DbtModelInfo[] sources: DbtSourceInfo[] + tests: DbtTestInfo[] source_count: number model_count: number test_count: number diff --git a/packages/opencode/src/altimate/tools/impact-analysis.ts b/packages/opencode/src/altimate/tools/impact-analysis.ts index 25c559bdd6..205811f81e 100644 --- a/packages/opencode/src/altimate/tools/impact-analysis.ts +++ b/packages/opencode/src/altimate/tools/impact-analysis.ts @@ -70,8 +70,15 @@ export const ImpactAnalysisTool = Tool.define("impact_analysis", { const direct = downstream.filter((d) => d.depth === 1) const transitive = downstream.filter((d) => d.depth > 1) - // Step 4: Report test count (manifest has test_count but not individual tests) - const affectedTestCount = manifest.test_count ?? 0 + // Step 4: Count only tests that reference the target model or its downstream models + const affectedModelIds = new Set([ + targetModel.unique_id, + ...downstream.map((d) => modelsByName.get(d.name)?.unique_id).filter(Boolean), + ]) + const affectedTests = (manifest.tests ?? []).filter((t) => + t.depends_on?.some((dep) => affectedModelIds.has(dep)), + ) + const affectedTestCount = affectedTests.length // Step 5: If column specified, attempt column-level lineage let columnImpact: string[] = [] @@ -253,9 +260,13 @@ export function formatImpactReport(data: { // Affected tests if (data.affectedTestCount > 0) { - lines.push(`Tests in project: ${data.affectedTestCount}`) + lines.push(`Affected tests: ${data.affectedTestCount}`) lines.push("".padEnd(40, "-")) - lines.push(` Run \`dbt test\` to verify all ${data.affectedTestCount} tests still pass after this change.`) + lines.push( + data.affectedTestCount === 1 + ? " Run `dbt test` to verify this test still passes after this change." + : ` Run \`dbt test\` to verify these ${data.affectedTestCount} tests still pass after this change.`, + ) lines.push("") } diff --git a/packages/opencode/src/util/locale.ts b/packages/opencode/src/util/locale.ts index 653da09a0b..1afe30bb81 100644 --- a/packages/opencode/src/util/locale.ts +++ b/packages/opencode/src/util/locale.ts @@ -54,8 +54,8 @@ export namespace Locale { const minutes = Math.floor((input % 3600000) / 60000) return `${hours}h ${minutes}m` } - const hours = Math.floor(input / 3600000) - const days = Math.floor((input % 3600000) / 86400000) + const days = Math.floor(input / 86400000) + const hours = Math.floor((input % 86400000) / 3600000) return `${days}d ${hours}h` } diff --git a/packages/opencode/test/altimate/tools/impact-analysis.test.ts b/packages/opencode/test/altimate/tools/impact-analysis.test.ts index f291e57d72..85c71a29df 100644 --- a/packages/opencode/test/altimate/tools/impact-analysis.test.ts +++ b/packages/opencode/test/altimate/tools/impact-analysis.test.ts @@ -151,7 +151,7 @@ describe("formatImpactReport", () => { }) expect(report).toContain("WARNING: Rename requires updating all downstream references.") expect(report).toContain("Update all downstream SQL references to new name") - expect(report).toContain("Tests in project: 5") + expect(report).toContain("Affected tests: 5") }) test("column-level impact shows affected columns", () => { diff --git a/packages/opencode/test/util/locale.test.ts b/packages/opencode/test/util/locale.test.ts index 9a0791983f..502b85b6aa 100644 --- a/packages/opencode/test/util/locale.test.ts +++ b/packages/opencode/test/util/locale.test.ts @@ -43,13 +43,10 @@ describe("Locale.duration", () => { expect(Locale.duration(5400000)).toBe("1h 30m") }) - // BUG: Locale.duration >=24h has swapped days/hours calculation. - // hours = Math.floor(input / 3600000) gives total hours (25), not remainder. - // days = Math.floor((input % 3600000) / 86400000) always yields 0. - // Correct: days = Math.floor(input / 86400000), hours = Math.floor((input % 86400000) / 3600000) - // 90000000ms = 25h = 1d 1h — should display "1d 1h" + // Fixed in this PR: days and hours were swapped for >=24h durations. + // 90000000ms = 25h = 1d 1h // See: https://github.com/AltimateAI/altimate-code/issues/368 - test.skip("FIXME: days and hours for >=24h are calculated correctly", () => { + test("days and hours for >=24h are calculated correctly", () => { expect(Locale.duration(90000000)).toBe("1d 1h") }) })