Skip to content

Commit 9a355a9

Browse files
authored
Local adapter gracefully fails if file doesnt exist (#21)
* local adapter gracefully fails if file doesnt exist * fixed tests * added missing header
1 parent 30f7306 commit 9a355a9

13 files changed

Lines changed: 179 additions & 8 deletions

File tree

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"google/cloud-secret-manager": "^2.2",
1010
"guzzlehttp/guzzle": "^7.0",
1111
"mockery/mockery": "^1.6.12",
12-
"phpunit/phpunit": "^10.5 || ^11.0",
12+
"phpunit/phpunit": "^10.5 || ^11.0 || ^12.0 || ^13.0",
1313
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0",
1414
"symfony/config": "^5.3 || ^6.0 || ^7.0 || ^8.0",
1515
"symfony/dependency-injection": "^5.0 || ^6.0 || ^7.0 || ^8.0",

phpunit.xml.dist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<!-- https://phpunit.de/manual/current/en/appendixes.configuration.html -->
3-
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd" colors="true" bootstrap="vendor/autoload.php" cacheDirectory=".phpunit.cache">
3+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd" colors="true" bootstrap="vendor/autoload.php" cacheDirectory=".phpunit.cache">
44
<php>
55
<ini name="error_reporting" value="-1"/>
66
<ini name="memory_limit" value="-1"/>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Tests export-ignore
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Tests export-ignore

src/Adapter/Chain/.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Tests export-ignore
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Tests export-ignore
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Tests export-ignore
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Tests export-ignore

src/Adapter/Local/JSONFile/LocalJSONFileAdapter.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,14 @@ public function __construct(array $config)
5353
*/
5454
public function getSecret(string $key, ?array $options = []): Secret
5555
{
56-
$secrets = $this->loadSecrets();
57-
$keys = array_column($secrets, 'key');
58-
$index = array_search($key, $keys, true);
56+
try {
57+
$secrets = $this->loadSecrets();
58+
} catch (\Exception $e) {
59+
throw new SecretNotFoundException($key, $e);
60+
}
61+
62+
$keys = array_column($secrets, 'key');
63+
$index = array_search($key, $keys, true);
5964

6065
if ($index === false || $index === null) {
6166
throw new SecretNotFoundException($key);
@@ -130,6 +135,10 @@ private static function updateValue(Secret $secret, array $secrets): array
130135
*/
131136
private function loadSecrets(): array
132137
{
138+
if (!file_exists($this->secretsFile)) {
139+
throw new \Exception('Secrets file does not exist.');
140+
}
141+
133142
return json_decode(file_get_contents($this->secretsFile), true, 512, JSON_THROW_ON_ERROR);
134143
}
135144

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* @author Aaron Scherer <aequasi@gmail.com>
7+
* @date 2019
8+
* @license https://opensource.org/licenses/MIT
9+
*/
10+
11+
namespace Secretary\Tests;
12+
13+
use PHPUnit\Framework\TestCase;
14+
use Secretary\Adapter\Local\JSONFile\LocalJSONFileAdapter;
15+
use Secretary\Exception\SecretNotFoundException;
16+
use Secretary\Secret;
17+
18+
class LocalJSONFileAdapterTest extends TestCase
19+
{
20+
private string $tempFile;
21+
22+
protected function setUp(): void
23+
{
24+
parent::setUp();
25+
26+
$this->tempFile = tempnam(sys_get_temp_dir(), 'secretary_test_');
27+
file_put_contents($this->tempFile, json_encode([
28+
['key' => 'db/password', 'value' => 's3cret'],
29+
['key' => 'api/token', 'value' => 'tok123', 'metadata' => ['env' => 'test']],
30+
]));
31+
}
32+
33+
protected function tearDown(): void
34+
{
35+
if (file_exists($this->tempFile)) {
36+
unlink($this->tempFile);
37+
}
38+
39+
parent::tearDown();
40+
}
41+
42+
public function testConstructThrowsWhenConfigIsEmpty(): void
43+
{
44+
$this->expectException(\Exception::class);
45+
$this->expectExceptionMessage('Configuration is required.');
46+
47+
new LocalJSONFileAdapter([]);
48+
}
49+
50+
public function testConstructThrowsWhenFileMissing(): void
51+
{
52+
$this->expectException(\Exception::class);
53+
$this->expectExceptionMessage('`file` is a required config.');
54+
55+
new LocalJSONFileAdapter(['foo' => 'bar']);
56+
}
57+
58+
public function testGetSecretReturnsSecret(): void
59+
{
60+
$adapter = new LocalJSONFileAdapter(['file' => $this->tempFile]);
61+
62+
$secret = $adapter->getSecret('db/password');
63+
64+
$this->assertInstanceOf(Secret::class, $secret);
65+
$this->assertSame('db/password', $secret->getKey());
66+
$this->assertSame('s3cret', $secret->getValue());
67+
}
68+
69+
public function testGetSecretReturnsMetadata(): void
70+
{
71+
$adapter = new LocalJSONFileAdapter(['file' => $this->tempFile]);
72+
73+
$secret = $adapter->getSecret('api/token');
74+
75+
$this->assertSame(['env' => 'test'], $secret->getMetadata());
76+
}
77+
78+
public function testGetSecretThrowsWhenKeyNotFound(): void
79+
{
80+
$adapter = new LocalJSONFileAdapter(['file' => $this->tempFile]);
81+
82+
$this->expectException(SecretNotFoundException::class);
83+
$this->expectExceptionMessage('No secret was found with the key: "nonexistent"');
84+
85+
$adapter->getSecret('nonexistent');
86+
}
87+
88+
public function testGetSecretThrowsWhenFileDoesNotExist(): void
89+
{
90+
$adapter = new LocalJSONFileAdapter(['file' => '/tmp/secretary_nonexistent_file.json']);
91+
92+
$this->expectException(SecretNotFoundException::class);
93+
$this->expectExceptionMessage('No secret was found with the key: "any-key"');
94+
95+
try {
96+
$adapter->getSecret('any-key');
97+
} catch (SecretNotFoundException $e) {
98+
$this->assertNotNull($e->getPrevious(), 'Expected a previous exception to be set');
99+
$this->assertSame('Secrets file does not exist.', $e->getPrevious()->getMessage());
100+
101+
throw $e;
102+
}
103+
}
104+
105+
public function testPutSecretAddsNewSecret(): void
106+
{
107+
$adapter = new LocalJSONFileAdapter(['file' => $this->tempFile]);
108+
$secret = new Secret('new/key', 'new-value');
109+
110+
$result = $adapter->putSecret($secret);
111+
112+
$this->assertSame('new/key', $result->getKey());
113+
$this->assertSame('new-value', $result->getValue());
114+
}
115+
116+
public function testPutSecretUpdatesExistingSecret(): void
117+
{
118+
$adapter = new LocalJSONFileAdapter(['file' => $this->tempFile]);
119+
$secret = new Secret('db/password', 'updated');
120+
121+
$adapter->putSecret($secret);
122+
123+
$retrieved = $adapter->getSecret('db/password');
124+
$this->assertSame('updated', $retrieved->getValue());
125+
}
126+
127+
public function testDeleteSecretByKey(): void
128+
{
129+
$adapter = new LocalJSONFileAdapter(['file' => $this->tempFile]);
130+
131+
$adapter->deleteSecretByKey('db/password');
132+
133+
$this->expectException(SecretNotFoundException::class);
134+
$adapter->getSecret('db/password');
135+
}
136+
137+
public function testDeleteSecretByKeyThrowsWhenNotFound(): void
138+
{
139+
$adapter = new LocalJSONFileAdapter(['file' => $this->tempFile]);
140+
141+
$this->expectException(SecretNotFoundException::class);
142+
$adapter->deleteSecretByKey('nonexistent');
143+
}
144+
145+
public function testDeleteSecret(): void
146+
{
147+
$adapter = new LocalJSONFileAdapter(['file' => $this->tempFile]);
148+
$secret = new Secret('api/token', 'tok123');
149+
150+
$adapter->deleteSecret($secret);
151+
152+
$this->expectException(SecretNotFoundException::class);
153+
$adapter->getSecret('api/token');
154+
}
155+
}

0 commit comments

Comments
 (0)