|
5 | 5 | let(:http_client) { Infisical::HTTPClient.new(base_url: base_url, sleeper: ->(_seconds) {}) } |
6 | 6 | let(:secrets) { described_class.new(http_client) } |
7 | 7 |
|
| 8 | + def secret_payload(key, value: "v", path: "/") |
| 9 | + { id: key.downcase, secretKey: key, secretValue: value, secretPath: path, version: 1, type: "shared" } |
| 10 | + end |
| 11 | + |
8 | 12 | describe "#list" do |
9 | | - it "lists secrets for a project/environment" do |
10 | | - stub_request(:get, "#{base_url}/api/v3/secrets/raw") |
11 | | - .with(query: hash_including("workspaceId" => "proj-1", "environment" => "dev")) |
| 13 | + it "lists secrets for a project/environment, sorted by key" do |
| 14 | + stub_request(:get, "#{base_url}/api/v4/secrets") |
| 15 | + .with(query: hash_including("projectId" => "proj-1", "environment" => "dev", |
| 16 | + "includeImports" => "true", "recursive" => "false")) |
12 | 17 | .to_return( |
13 | 18 | status: 200, |
14 | | - body: { secrets: [{ id: "1", workspace: "proj-1", environment: "dev", secretKey: "FOO", |
15 | | - secretValue: "bar", secretPath: "/", version: 1, type: "shared" }] }.to_json |
| 19 | + body: { secrets: [secret_payload("FOO", value: "bar"), secret_payload("BAR")] }.to_json |
16 | 20 | ) |
17 | 21 |
|
18 | 22 | result = secrets.list(project_id: "proj-1", environment: "dev") |
19 | 23 |
|
20 | | - expect(result.size).to eq(1) |
21 | | - expect(result.first).to be_a(Infisical::Models::Secret) |
22 | | - expect(result.first.secret_key).to eq("FOO") |
23 | | - expect(result.first.secret_value).to eq("bar") |
| 24 | + expect(result.map(&:secret_key)).to eq(%w[BAR FOO]) |
| 25 | + expect(result).to all(be_a(Infisical::Models::Secret)) |
| 26 | + expect(result.last.secret_value).to eq("bar") |
| 27 | + end |
| 28 | + |
| 29 | + it "does not send expandSecretReferences (the v4 API defaults it to true)" do |
| 30 | + stub = stub_request(:get, "#{base_url}/api/v4/secrets") |
| 31 | + .with(query: hash_excluding("expandSecretReferences")) |
| 32 | + .to_return(status: 200, body: { secrets: [] }.to_json) |
| 33 | + |
| 34 | + secrets.list(project_id: "proj-1", environment: "dev") |
| 35 | + |
| 36 | + expect(stub).to have_been_requested |
| 37 | + end |
| 38 | + |
| 39 | + context "when recursive" do |
| 40 | + it "collapses duplicate keys from different paths, keeping the last occurrence" do |
| 41 | + stub_request(:get, "#{base_url}/api/v4/secrets") |
| 42 | + .with(query: hash_including("recursive" => "true")) |
| 43 | + .to_return( |
| 44 | + status: 200, |
| 45 | + body: { secrets: [secret_payload("FOO", value: "root", path: "/"), |
| 46 | + secret_payload("FOO", value: "nested", path: "/app"), |
| 47 | + secret_payload("BAR")] }.to_json |
| 48 | + ) |
| 49 | + |
| 50 | + result = secrets.list(project_id: "proj-1", environment: "dev", recursive: true) |
| 51 | + |
| 52 | + expect(result.map(&:secret_key)).to eq(%w[BAR FOO]) |
| 53 | + expect(result.last.secret_value).to eq("nested") |
| 54 | + end |
| 55 | + |
| 56 | + it "keeps same-named secrets from different paths with skip_unique_validation" do |
| 57 | + stub_request(:get, "#{base_url}/api/v4/secrets") |
| 58 | + .with(query: hash_including("recursive" => "true")) |
| 59 | + .to_return( |
| 60 | + status: 200, |
| 61 | + body: { secrets: [secret_payload("FOO", value: "root", path: "/"), |
| 62 | + secret_payload("FOO", value: "nested", path: "/app")] }.to_json |
| 63 | + ) |
| 64 | + |
| 65 | + result = secrets.list(project_id: "proj-1", environment: "dev", |
| 66 | + recursive: true, skip_unique_validation: true) |
| 67 | + |
| 68 | + expect(result.map(&:secret_value)).to contain_exactly("root", "nested") |
| 69 | + end |
| 70 | + end |
| 71 | + |
| 72 | + context "when include_imports" do |
| 73 | + it "appends imported secrets, with direct secrets taking precedence on key conflicts" do |
| 74 | + stub_request(:get, "#{base_url}/api/v4/secrets") |
| 75 | + .with(query: hash_including("includeImports" => "true")) |
| 76 | + .to_return( |
| 77 | + status: 200, |
| 78 | + body: { |
| 79 | + secrets: [secret_payload("FOO", value: "direct")], |
| 80 | + imports: [ |
| 81 | + { secretPath: "/shared", environment: "dev", |
| 82 | + secrets: [secret_payload("FOO", value: "imported"), secret_payload("DB_URL")] }, |
| 83 | + { secretPath: "/other", environment: "dev", |
| 84 | + secrets: [secret_payload("DB_URL", value: "later-import")] } |
| 85 | + ] |
| 86 | + }.to_json |
| 87 | + ) |
| 88 | + |
| 89 | + result = secrets.list(project_id: "proj-1", environment: "dev", include_imports: true) |
| 90 | + |
| 91 | + expect(result.map(&:secret_key)).to eq(%w[DB_URL FOO]) |
| 92 | + expect(result.find { |s| s.secret_key == "FOO" }.secret_value).to eq("direct") |
| 93 | + expect(result.find { |s| s.secret_key == "DB_URL" }.secret_value).to eq("v") |
| 94 | + end |
24 | 95 | end |
25 | 96 | end |
26 | 97 |
|
27 | 98 | describe "#get" do |
28 | 99 | it "fetches a single secret by name" do |
29 | | - stub_request(:get, "#{base_url}/api/v3/secrets/raw/FOO") |
30 | | - .with(query: hash_including("workspaceId" => "proj-1", "environment" => "dev")) |
| 100 | + stub_request(:get, "#{base_url}/api/v4/secrets/FOO") |
| 101 | + .with(query: hash_including("projectId" => "proj-1", "environment" => "dev")) |
31 | 102 | .to_return(status: 200, body: { secret: { id: "1", secretKey: "FOO", secretValue: "bar" } }.to_json) |
32 | 103 |
|
33 | 104 | secret = secrets.get("FOO", project_id: "proj-1", environment: "dev") |
|
37 | 108 | end |
38 | 109 |
|
39 | 110 | it "URL-encodes secret names containing reserved characters" do |
40 | | - stub = stub_request(:get, "#{base_url}/api/v3/secrets/raw/FOO%2FBAR") |
41 | | - .with(query: hash_including("workspaceId" => "proj-1")) |
| 111 | + stub = stub_request(:get, "#{base_url}/api/v4/secrets/FOO%2FBAR") |
| 112 | + .with(query: hash_including("projectId" => "proj-1")) |
42 | 113 | .to_return(status: 200, body: { secret: { id: "1", secretKey: "FOO/BAR" } }.to_json) |
43 | 114 |
|
44 | 115 | secrets.get("FOO/BAR", project_id: "proj-1", environment: "dev") |
|
49 | 120 |
|
50 | 121 | describe "#create" do |
51 | 122 | it "creates a secret with the given value" do |
52 | | - stub = stub_request(:post, "#{base_url}/api/v3/secrets/raw/FOO") |
53 | | - .with(body: hash_including("workspaceId" => "proj-1", "environment" => "dev", "secretValue" => "bar")) |
| 123 | + stub = stub_request(:post, "#{base_url}/api/v4/secrets/FOO") |
| 124 | + .with(body: hash_including("projectId" => "proj-1", "environment" => "dev", "secretValue" => "bar")) |
54 | 125 | .to_return(status: 200, body: { secret: { id: "1", secretKey: "FOO", secretValue: "bar" } }.to_json) |
55 | 126 |
|
56 | 127 | secret = secrets.create("FOO", "bar", project_id: "proj-1", environment: "dev") |
|
62 | 133 |
|
63 | 134 | describe "#update" do |
64 | 135 | it "updates a secret's value" do |
65 | | - stub = stub_request(:patch, "#{base_url}/api/v3/secrets/raw/FOO") |
66 | | - .with(body: hash_including("secretValue" => "new-val")) |
| 136 | + stub = stub_request(:patch, "#{base_url}/api/v4/secrets/FOO") |
| 137 | + .with(body: hash_including("projectId" => "proj-1", "secretValue" => "new-val")) |
67 | 138 | .to_return(status: 200, body: { secret: { id: "1", secretKey: "FOO", secretValue: "new-val" } }.to_json) |
68 | 139 |
|
69 | 140 | secret = secrets.update("FOO", project_id: "proj-1", environment: "dev", secret_value: "new-val") |
|
73 | 144 | end |
74 | 145 |
|
75 | 146 | it "supports renaming via new_secret_name" do |
76 | | - stub = stub_request(:patch, "#{base_url}/api/v3/secrets/raw/FOO") |
| 147 | + stub = stub_request(:patch, "#{base_url}/api/v4/secrets/FOO") |
77 | 148 | .with(body: hash_including("newSecretName" => "BAR")) |
78 | 149 | .to_return(status: 200, body: { secret: { id: "1", secretKey: "BAR" } }.to_json) |
79 | 150 |
|
|
90 | 161 |
|
91 | 162 | describe "#delete" do |
92 | 163 | it "deletes a secret by name" do |
93 | | - stub = stub_request(:delete, "#{base_url}/api/v3/secrets/raw/FOO") |
94 | | - .with(body: hash_including("workspaceId" => "proj-1", "environment" => "dev")) |
| 164 | + stub = stub_request(:delete, "#{base_url}/api/v4/secrets/FOO") |
| 165 | + .with(body: hash_including("projectId" => "proj-1", "environment" => "dev")) |
95 | 166 | .to_return(status: 200, body: { secret: { id: "1", secretKey: "FOO" } }.to_json) |
96 | 167 |
|
97 | 168 | secret = secrets.delete("FOO", project_id: "proj-1", environment: "dev") |
|
0 commit comments