|
4 | 4 | validateDependencies, |
5 | 5 | detectCyclicDependencies, |
6 | 6 | topologicalSort, |
7 | | - reverseTopologicalSort |
| 7 | + reverseTopologicalSort, |
| 8 | + printDependencyGraph |
8 | 9 | } from "../src/dependency-resolver.js" |
9 | 10 | import { Schema } from "../src/model.js" |
10 | 11 | import { |
@@ -220,6 +221,7 @@ describe("Dependency Resolver", () => { |
220 | 221 | }) |
221 | 222 |
|
222 | 223 | describe("topologicalSort", () => { |
| 224 | + |
223 | 225 | it("should sort components without dependencies in original order", () => { |
224 | 226 | const components: Array<Schema.DeployableComponent> = [ |
225 | 227 | { |
@@ -249,6 +251,35 @@ describe("Dependency Resolver", () => { |
249 | 251 | expect(result.sortedComponents.map(c => c.id)).to.deep.equal(["component-a", "component-b", "component-c"]) |
250 | 252 | }) |
251 | 253 |
|
| 254 | + it("should preserve original order if no dependsOn fields are present", () => { |
| 255 | + const components: Array<Schema.DeployableComponent> = [ |
| 256 | + { |
| 257 | + name: "Component X", |
| 258 | + id: "component-x", |
| 259 | + location: { type: Schema.LocationType.Local }, |
| 260 | + deploy: { command: "deploy.sh" }, |
| 261 | + undeploy: { command: "undeploy.sh" } |
| 262 | + }, |
| 263 | + { |
| 264 | + name: "Component Y", |
| 265 | + id: "component-y", |
| 266 | + location: { type: Schema.LocationType.Local }, |
| 267 | + deploy: { command: "deploy.sh" }, |
| 268 | + undeploy: { command: "undeploy.sh" } |
| 269 | + }, |
| 270 | + { |
| 271 | + name: "Component Z", |
| 272 | + id: "component-z", |
| 273 | + location: { type: Schema.LocationType.Local }, |
| 274 | + deploy: { command: "deploy.sh" }, |
| 275 | + undeploy: { command: "undeploy.sh" } |
| 276 | + } |
| 277 | + ] |
| 278 | + |
| 279 | + const result = topologicalSort(components) |
| 280 | + expect(result.sortedComponents.map(c => c.id)).to.deep.equal(["component-x", "component-y", "component-z"]) |
| 281 | + }) |
| 282 | + |
252 | 283 | it("should sort components with dependencies correctly", () => { |
253 | 284 | const components: Array<Schema.DeployableComponent> = [ |
254 | 285 | { |
@@ -360,4 +391,72 @@ describe("Dependency Resolver", () => { |
360 | 391 | expect(reverseResult.map(c => c.id)).to.deep.equal(["frontend", "api-service", "cache", "database"]) |
361 | 392 | }) |
362 | 393 | }) |
| 394 | + |
| 395 | + describe("printDependencyGraph", () => { |
| 396 | + let logOutput: string[] |
| 397 | + let originalLog: typeof console.log |
| 398 | + beforeEach(() => { |
| 399 | + logOutput = [] |
| 400 | + originalLog = console.log |
| 401 | + console.log = (msg?: any) => logOutput.push(String(msg)) |
| 402 | + }) |
| 403 | + afterEach(() => { |
| 404 | + console.log = originalLog |
| 405 | + }) |
| 406 | + |
| 407 | + it("prints graph for components without dependencies", () => { |
| 408 | + const components: Array<Schema.DeployableComponent> = [ |
| 409 | + { name: "A", id: "a", location: { type: Schema.LocationType.Local }, deploy: { command: "deploy.sh" }, undeploy: { command: "undeploy.sh" } }, |
| 410 | + { name: "B", id: "b", location: { type: Schema.LocationType.Local }, deploy: { command: "deploy.sh" }, undeploy: { command: "undeploy.sh" } }, |
| 411 | + { name: "C", id: "c", location: { type: Schema.LocationType.Local }, deploy: { command: "deploy.sh" }, undeploy: { command: "undeploy.sh" } } |
| 412 | + ] |
| 413 | + printDependencyGraph(components) |
| 414 | + expect(logOutput[0]).to.equal("Dependency Graph") |
| 415 | + expect(logOutput).to.include(" Stage 1 │ a b c") |
| 416 | + }) |
| 417 | + |
| 418 | + it("prints graph for components with dependencies at multiple stages", () => { |
| 419 | + const components: Array<Schema.DeployableComponent> = [ |
| 420 | + { name: "DB", id: "db", location: { type: Schema.LocationType.Local }, deploy: { command: "deploy.sh" }, undeploy: { command: "undeploy.sh" }, dependsOn: [] }, |
| 421 | + { name: "API", id: "api", location: { type: Schema.LocationType.Local }, deploy: { command: "deploy.sh" }, undeploy: { command: "undeploy.sh" }, dependsOn: ["db"] }, |
| 422 | + { name: "Cache", id: "cache", location: { type: Schema.LocationType.Local }, deploy: { command: "deploy.sh" }, undeploy: { command: "undeploy.sh" }, dependsOn: ["db"] } |
| 423 | + ] |
| 424 | + printDependencyGraph(components) |
| 425 | + expect(logOutput[0]).to.equal("Dependency Graph") |
| 426 | + expect(logOutput).to.include(" Stage 1 │ db") |
| 427 | + expect(logOutput).to.include(" Stage 2 │ api cache") |
| 428 | + expect(logOutput).to.include(" db ──▶ api") |
| 429 | + expect(logOutput).to.include(" db ──▶ cache") |
| 430 | + }) |
| 431 | + |
| 432 | + it("prints graph for chained dependencies", () => { |
| 433 | + const components: Array<Schema.DeployableComponent> = [ |
| 434 | + { name: "A", id: "a", location: { type: Schema.LocationType.Local }, deploy: { command: "deploy.sh" }, undeploy: { command: "undeploy.sh" }, dependsOn: [] }, |
| 435 | + { name: "B", id: "b", location: { type: Schema.LocationType.Local }, deploy: { command: "deploy.sh" }, undeploy: { command: "undeploy.sh" }, dependsOn: ["a"] }, |
| 436 | + { name: "C", id: "c", location: { type: Schema.LocationType.Local }, deploy: { command: "deploy.sh" }, undeploy: { command: "undeploy.sh" }, dependsOn: ["b"] } |
| 437 | + ] |
| 438 | + printDependencyGraph(components) |
| 439 | + expect(logOutput[0]).to.equal("Dependency Graph") |
| 440 | + expect(logOutput).to.include(" Stage 1 │ a") |
| 441 | + expect(logOutput).to.include(" Stage 2 │ b") |
| 442 | + expect(logOutput).to.include(" Stage 3 │ c") |
| 443 | + expect(logOutput).to.include(" a ──▶ b") |
| 444 | + expect(logOutput).to.include(" b ──▶ c") |
| 445 | + }) |
| 446 | + |
| 447 | + it("annotates parallel components with ⚡ and prints a legend", () => { |
| 448 | + const components: Array<Schema.DeployableComponent> = [ |
| 449 | + { name: "A", id: "a", location: { type: Schema.LocationType.Local }, deploy: { command: "deploy.sh" }, undeploy: { command: "undeploy.sh" } }, |
| 450 | + { name: "B", id: "b", location: { type: Schema.LocationType.Local }, deploy: { command: "deploy.sh" }, undeploy: { command: "undeploy.sh" }, dependsOn: ["a"], parallel: true }, |
| 451 | + { name: "C", id: "c", location: { type: Schema.LocationType.Local }, deploy: { command: "deploy.sh" }, undeploy: { command: "undeploy.sh" }, dependsOn: ["a"], parallel: true } |
| 452 | + ] |
| 453 | + printDependencyGraph(components) |
| 454 | + expect(logOutput[0]).to.equal("Dependency Graph") |
| 455 | + expect(logOutput).to.include(" Stage 1 │ a") |
| 456 | + expect(logOutput).to.include(" Stage 2 │ b ⚡ c ⚡") |
| 457 | + expect(logOutput).to.include(" a ──▶ b") |
| 458 | + expect(logOutput).to.include(" a ──▶ c") |
| 459 | + expect(logOutput).to.include(" ⚡ = deployed concurrently within stage") |
| 460 | + }) |
| 461 | + }) |
363 | 462 | }) |
0 commit comments