refactor: use system secrets when possible (#248)

This commit is contained in:
Kainoa Kanter
2025-12-29 12:09:52 -08:00
committed by GitHub
parent 4927a915f9
commit fab0ca676b
12 changed files with 869 additions and 57 deletions

191
src/tests/secrets.test.ts Normal file
View File

@@ -0,0 +1,191 @@
// src/tests/keychain.test.ts
// Tests for secrets utility functions
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import {
deleteApiKey,
deleteRefreshToken,
deleteSecureTokens,
getApiKey,
getRefreshToken,
getSecureTokens,
isKeychainAvailable,
keychainAvailablePrecompute,
type SecureTokens,
setApiKey,
setRefreshToken,
setSecureTokens,
} from "../utils/secrets";
describe("Secrets utilities", () => {
beforeEach(async () => {
if (keychainAvailablePrecompute) {
await deleteSecureTokens();
}
});
afterEach(async () => {
if (keychainAvailablePrecompute) {
await deleteSecureTokens();
}
});
test("isKeychainAvailable works", async () => {
const available = await isKeychainAvailable();
expect(typeof available).toBe("boolean");
});
test.skipIf(!keychainAvailablePrecompute)(
"can store and retrieve API key",
async () => {
const testApiKey = "sk-test-api-key-12345";
await setApiKey(testApiKey);
const retrievedApiKey = await getApiKey();
expect(retrievedApiKey).toBe(testApiKey);
},
);
test.skipIf(!keychainAvailablePrecompute)(
"can store and retrieve refresh token",
async () => {
const testRefreshToken = "rt-test-refresh-token-67890";
await setRefreshToken(testRefreshToken);
const retrievedRefreshToken = await getRefreshToken();
expect(retrievedRefreshToken).toBe(testRefreshToken);
},
);
test.skipIf(!keychainAvailablePrecompute)(
"can store and retrieve both tokens together",
async () => {
const tokens: SecureTokens = {
apiKey: "sk-test-api-key-combined",
refreshToken: "rt-test-refresh-token-combined",
};
await setSecureTokens(tokens);
const retrievedTokens = await getSecureTokens();
expect(retrievedTokens.apiKey).toBe(tokens.apiKey);
expect(retrievedTokens.refreshToken).toBe(tokens.refreshToken);
},
);
test.skipIf(!keychainAvailablePrecompute)("can delete API key", async () => {
const testApiKey = "sk-test-api-key-delete";
await setApiKey(testApiKey);
let retrievedApiKey = await getApiKey();
expect(retrievedApiKey).toBe(testApiKey);
await deleteApiKey();
retrievedApiKey = await getApiKey();
expect(retrievedApiKey).toBe(null);
});
test.skipIf(!keychainAvailablePrecompute)(
"can delete refresh token",
async () => {
const testRefreshToken = "rt-test-refresh-token-delete";
await setRefreshToken(testRefreshToken);
let retrievedRefreshToken = await getRefreshToken();
expect(retrievedRefreshToken).toBe(testRefreshToken);
await deleteRefreshToken();
retrievedRefreshToken = await getRefreshToken();
expect(retrievedRefreshToken).toBe(null);
},
);
test.skipIf(!keychainAvailablePrecompute)(
"can delete all tokens",
async () => {
const tokens: SecureTokens = {
apiKey: "sk-test-api-key-delete-all",
refreshToken: "rt-test-refresh-token-delete-all",
};
await setSecureTokens(tokens);
let retrievedTokens = await getSecureTokens();
expect(retrievedTokens.apiKey).toBe(tokens.apiKey);
expect(retrievedTokens.refreshToken).toBe(tokens.refreshToken);
await deleteSecureTokens();
retrievedTokens = await getSecureTokens();
expect(retrievedTokens.apiKey).toBeUndefined();
expect(retrievedTokens.refreshToken).toBeUndefined();
},
);
test.skipIf(!keychainAvailablePrecompute)(
"returns null for non-existent tokens",
async () => {
// Ensure no tokens exist
await deleteSecureTokens();
const apiKey = await getApiKey();
const refreshToken = await getRefreshToken();
const tokens = await getSecureTokens();
expect(apiKey).toBe(null);
expect(refreshToken).toBe(null);
expect(tokens.apiKey).toBeUndefined();
expect(tokens.refreshToken).toBeUndefined();
},
);
test.skipIf(!keychainAvailablePrecompute)(
"handles partial token storage",
async () => {
// Store only API key
await setSecureTokens({ apiKey: "sk-only-api-key" });
let tokens = await getSecureTokens();
expect(tokens.apiKey).toBe("sk-only-api-key");
expect(tokens.refreshToken).toBeUndefined();
// Clean up and store only refresh token
await deleteSecureTokens();
await setSecureTokens({ refreshToken: "rt-only-refresh-token" });
tokens = await getSecureTokens();
expect(tokens.apiKey).toBeUndefined();
expect(tokens.refreshToken).toBe("rt-only-refresh-token");
},
);
test("gracefully handles secrets unavailability", async () => {
// This test should work even if secrets are not available
if (await isKeychainAvailable()) {
// If secrets are available, this is a basic functionality test
const tokens = await getSecureTokens();
expect(typeof tokens).toBe("object");
} else {
// If secrets are not available, functions should return null or throw appropriately
const tokens = await getSecureTokens();
expect(tokens.apiKey).toBeUndefined();
expect(tokens.refreshToken).toBeUndefined();
const apiKey = await getApiKey();
expect(apiKey).toBe(null);
const refreshToken = await getRefreshToken();
expect(refreshToken).toBe(null);
// Set operations should throw when secrets unavailable (handled by settings manager)
await expect(setSecureTokens({ apiKey: "test" })).rejects.toThrow();
await expect(setApiKey("test")).rejects.toThrow();
await expect(setRefreshToken("test")).rejects.toThrow();
// Delete operations should not throw (no-op when secrets unavailable)
await expect(deleteSecureTokens()).resolves.toBeUndefined();
await expect(deleteApiKey()).resolves.toBeUndefined();
await expect(deleteRefreshToken()).resolves.toBeUndefined();
}
});
});