192 lines
5.8 KiB
TypeScript
192 lines
5.8 KiB
TypeScript
// 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();
|
|
}
|
|
});
|
|
});
|