import { assertEquals, assertRejects } from 'jsr:@std/assert'; import { describe, it } from 'jsr:@std/testing/bdd'; import { TypedRequest } from '@api.global/typedrequest'; import { createTestContainer, getTestPorts, loginAndGetIdentity, TEST_ACCESS_KEY, } from './helpers/server.helper.ts'; import { ObjectStorageContainer } from '../ts/index.ts'; import type { IReq_CreateBucket, IReq_ListBuckets } from '../ts_interfaces/requests/buckets.ts'; import type { IReq_AddCredential, IReq_GetCredentials, IReq_RemoveCredential, } from '../ts_interfaces/requests/credentials.ts'; import type * as interfaces from '../ts_interfaces/index.ts'; const PORT_INDEX = 8; const ports = getTestPorts(PORT_INDEX); const url = `http://localhost:${ports.uiPort}/typedrequest`; const storageDirectory = `.nogit/testdata-${PORT_INDEX}`; const cleanupStorageDirectory = async () => { try { await Deno.remove(storageDirectory, { recursive: true }); } catch (error) { if (!(error instanceof Deno.errors.NotFound)) { throw error; } } }; describe('Credential persistence', { sanitizeResources: false, sanitizeOps: false }, () => { it('persists managed credentials across restart and refreshes the internal client', async () => { await cleanupStorageDirectory(); let activeContainer: ObjectStorageContainer | null = null; const stopContainer = async () => { if (!activeContainer) { return; } try { await activeContainer.stop(); } finally { activeContainer = null; } }; try { activeContainer = createTestContainer(PORT_INDEX); await activeContainer.start(); let identity: interfaces.data.IIdentity = await loginAndGetIdentity(ports.uiPort); const addCredential = new TypedRequest(url, 'addCredential'); await addCredential.fire({ identity, accessKeyId: 'persisted-key', secretAccessKey: 'persisted-secret', }); const removeCredential = new TypedRequest(url, 'removeCredential'); await removeCredential.fire({ identity, accessKeyId: TEST_ACCESS_KEY }); const getCredentials = new TypedRequest(url, 'getCredentials'); const credentialsBeforeRestart = await getCredentials.fire({ identity }); assertEquals(credentialsBeforeRestart.credentials.length, 1); assertEquals(credentialsBeforeRestart.credentials[0].accessKeyId, 'persisted-key'); const listBuckets = new TypedRequest(url, 'listBuckets'); const bucketsBeforeRestart = await listBuckets.fire({ identity }); assertEquals(Array.isArray(bucketsBeforeRestart.buckets), true); await stopContainer(); activeContainer = createTestContainer(PORT_INDEX); await activeContainer.start(); identity = await loginAndGetIdentity(ports.uiPort); const credentialsAfterRestart = await getCredentials.fire({ identity }); assertEquals(credentialsAfterRestart.credentials.length, 1); assertEquals(credentialsAfterRestart.credentials[0].accessKeyId, 'persisted-key'); const createBucket = new TypedRequest(url, 'createBucket'); await createBucket.fire({ identity, bucketName: 'persisted-creds-bucket' }); const bucketsAfterRestart = await listBuckets.fire({ identity }); assertEquals( bucketsAfterRestart.buckets.some((bucket) => bucket.name === 'persisted-creds-bucket'), true, ); } finally { await stopContainer(); await cleanupStorageDirectory(); } }); it('lets explicit environment credentials override persisted managed credentials', async () => { const portIndex = 10; const envPorts = getTestPorts(portIndex); const envUrl = `http://localhost:${envPorts.uiPort}/typedrequest`; const envStorageDirectory = `.nogit/testdata-${portIndex}`; const previousAccessKey = Deno.env.get('OBJST_ACCESS_KEY'); const previousSecretKey = Deno.env.get('OBJST_SECRET_KEY'); let container: ObjectStorageContainer | null = null; const cleanupEnvStorageDirectory = async () => { try { await Deno.remove(envStorageDirectory, { recursive: true }); } catch (error) { if (!(error instanceof Deno.errors.NotFound)) { throw error; } } }; try { await cleanupEnvStorageDirectory(); await Deno.mkdir(`${envStorageDirectory}/.objectstorage`, { recursive: true }); await Deno.writeTextFile( `${envStorageDirectory}/.objectstorage/admin-config.json`, JSON.stringify({ accessCredentials: [{ accessKeyId: 'persisted-key', secretAccessKey: 'persisted-secret' }], }), ); Deno.env.set('OBJST_ACCESS_KEY', 'env-key'); Deno.env.set('OBJST_SECRET_KEY', 'env-secret'); container = createTestContainer(portIndex, { storageDirectory: envStorageDirectory }); await container.start(); const identity = await loginAndGetIdentity(envPorts.uiPort); const getCredentials = new TypedRequest(envUrl, 'getCredentials'); const response = await getCredentials.fire({ identity }); assertEquals(response.credentials.length, 1); assertEquals(response.credentials[0].accessKeyId, 'env-key'); } finally { if (container) { await container.stop(); } if (previousAccessKey === undefined) { Deno.env.delete('OBJST_ACCESS_KEY'); } else { Deno.env.set('OBJST_ACCESS_KEY', previousAccessKey); } if (previousSecretKey === undefined) { Deno.env.delete('OBJST_SECRET_KEY'); } else { Deno.env.set('OBJST_SECRET_KEY', previousSecretKey); } await cleanupEnvStorageDirectory(); } }); it('does not persist rejected credential replacements', async () => { const portIndex = 11; const rejectPorts = getTestPorts(portIndex); const rejectUrl = `http://localhost:${rejectPorts.uiPort}/typedrequest`; const rejectStorageDirectory = `.nogit/testdata-${portIndex}`; let container: ObjectStorageContainer | null = null; const cleanupRejectStorageDirectory = async () => { try { await Deno.remove(rejectStorageDirectory, { recursive: true }); } catch (error) { if (!(error instanceof Deno.errors.NotFound)) { throw error; } } }; try { await cleanupRejectStorageDirectory(); container = createTestContainer(portIndex, { storageDirectory: rejectStorageDirectory }); await container.start(); await assertRejects(() => container!.replaceAccessCredentials([ { accessKeyId: 'duplicate-key', secretAccessKey: 'secret-a' }, { accessKeyId: 'duplicate-key', secretAccessKey: 'secret-b' }, ]) ); const identity = await loginAndGetIdentity(rejectPorts.uiPort); const getCredentials = new TypedRequest(rejectUrl, 'getCredentials'); const response = await getCredentials.fire({ identity }); assertEquals(response.credentials.length, 1); assertEquals(response.credentials[0].accessKeyId, TEST_ACCESS_KEY); await assertRejects(() => Deno.readTextFile(`${rejectStorageDirectory}/.objectstorage/admin-config.json`) ); } finally { if (container) { await container.stop(); } await cleanupRejectStorageDirectory(); } }); });