File size: 3,748 Bytes
fc93158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import { Command } from "commander";
import { describe, expect, it } from "vitest";
import { inheritOptionFromParent } from "./command-options.js";

function attachRunCommandAndCaptureInheritedToken(command: Command) {
  let inherited: string | undefined;
  command
    .command("run")
    .option("--token <token>", "Run token")
    .action((_opts, childCommand) => {
      inherited = inheritOptionFromParent<string>(childCommand, "token");
    });
  return () => inherited;
}

describe("inheritOptionFromParent", () => {
  it.each([
    {
      label: "inherits from grandparent when parent does not define the option",
      parentHasTokenOption: false,
      argv: ["--token", "root-token", "gateway", "run"],
      expected: "root-token",
    },
    {
      label: "prefers nearest ancestor value when multiple ancestors set the same option",
      parentHasTokenOption: true,
      argv: ["--token", "root-token", "gateway", "--token", "gateway-token", "run"],
      expected: "gateway-token",
    },
  ])("$label", async ({ parentHasTokenOption, argv, expected }) => {
    const program = new Command().option("--token <token>", "Root token");
    const gateway = parentHasTokenOption
      ? program.command("gateway").option("--token <token>", "Gateway token")
      : program.command("gateway");
    const getInherited = attachRunCommandAndCaptureInheritedToken(gateway);

    await program.parseAsync(argv, { from: "user" });
    expect(getInherited()).toBe(expected);
  });

  it("does not inherit when the child option was set explicitly", async () => {
    const program = new Command().option("--token <token>", "Root token");
    const gateway = program.command("gateway").option("--token <token>", "Gateway token");
    const run = gateway.command("run").option("--token <token>", "Run token");

    program.setOptionValueWithSource("token", "root-token", "cli");
    gateway.setOptionValueWithSource("token", "gateway-token", "cli");
    run.setOptionValueWithSource("token", "run-token", "cli");

    expect(inheritOptionFromParent<string>(run, "token")).toBeUndefined();
  });

  it("does not inherit from ancestors beyond the bounded traversal depth", async () => {
    const program = new Command().option("--token <token>", "Root token");
    const level1 = program.command("level1");
    const level2 = level1.command("level2");
    const getInherited = attachRunCommandAndCaptureInheritedToken(level2);

    await program.parseAsync(["--token", "root-token", "level1", "level2", "run"], {
      from: "user",
    });
    expect(getInherited()).toBeUndefined();
  });

  it("inherits values from non-default ancestor sources (for example env)", () => {
    const program = new Command().option("--token <token>", "Root token");
    const gateway = program.command("gateway").option("--token <token>", "Gateway token");
    const run = gateway.command("run").option("--token <token>", "Run token");

    gateway.setOptionValueWithSource("token", "gateway-env-token", "env");

    expect(inheritOptionFromParent<string>(run, "token")).toBe("gateway-env-token");
  });

  it("skips default-valued ancestor options and keeps traversing", async () => {
    const program = new Command().option("--token <token>", "Root token");
    const gateway = program
      .command("gateway")
      .option("--token <token>", "Gateway token", "default");
    const getInherited = attachRunCommandAndCaptureInheritedToken(gateway);

    await program.parseAsync(["--token", "root-token", "gateway", "run"], {
      from: "user",
    });
    expect(getInherited()).toBe("root-token");
  });

  it("returns undefined when command is missing", () => {
    expect(inheritOptionFromParent<string>(undefined, "token")).toBeUndefined();
  });
});