mirror of
https://github.com/wkulhanek/bonob.git
synced 2025-12-21 17:33:29 +01:00
Replace json config with SQLite database
This commit is contained in:
234
tests/sqlite_smapi_token_store.test.ts
Normal file
234
tests/sqlite_smapi_token_store.test.ts
Normal file
@@ -0,0 +1,234 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { SQLiteSmapiTokenStore } from "../src/sqlite_smapi_token_store";
|
||||
import { SmapiToken } from "../src/smapi_auth";
|
||||
import { JWTSmapiLoginTokens } from "../src/smapi_auth";
|
||||
import { SystemClock } from "../src/clock";
|
||||
|
||||
describe("SQLiteSmapiTokenStore", () => {
|
||||
const testDbPath = path.join(__dirname, "test-tokens.db");
|
||||
const testJsonPath = path.join(__dirname, "test-tokens.json");
|
||||
let tokenStore: SQLiteSmapiTokenStore;
|
||||
|
||||
beforeEach(() => {
|
||||
// Clean up any existing test files
|
||||
if (fs.existsSync(testDbPath)) {
|
||||
fs.unlinkSync(testDbPath);
|
||||
}
|
||||
if (fs.existsSync(testJsonPath)) {
|
||||
fs.unlinkSync(testJsonPath);
|
||||
}
|
||||
if (fs.existsSync(`${testJsonPath}.bak`)) {
|
||||
fs.unlinkSync(`${testJsonPath}.bak`);
|
||||
}
|
||||
|
||||
tokenStore = new SQLiteSmapiTokenStore(testDbPath);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
tokenStore.close();
|
||||
|
||||
// Clean up test files
|
||||
if (fs.existsSync(testDbPath)) {
|
||||
fs.unlinkSync(testDbPath);
|
||||
}
|
||||
if (fs.existsSync(testJsonPath)) {
|
||||
fs.unlinkSync(testJsonPath);
|
||||
}
|
||||
if (fs.existsSync(`${testJsonPath}.bak`)) {
|
||||
fs.unlinkSync(`${testJsonPath}.bak`);
|
||||
}
|
||||
});
|
||||
|
||||
describe("Database Initialization", () => {
|
||||
it("should create database file on initialization", () => {
|
||||
expect(fs.existsSync(testDbPath)).toBe(true);
|
||||
});
|
||||
|
||||
it("should create parent directory if it doesn't exist", () => {
|
||||
const nestedPath = path.join(__dirname, "nested", "dir", "tokens.db");
|
||||
const nestedStore = new SQLiteSmapiTokenStore(nestedPath);
|
||||
|
||||
expect(fs.existsSync(nestedPath)).toBe(true);
|
||||
|
||||
nestedStore.close();
|
||||
fs.unlinkSync(nestedPath);
|
||||
fs.rmdirSync(path.dirname(nestedPath));
|
||||
fs.rmdirSync(path.dirname(path.dirname(nestedPath)));
|
||||
});
|
||||
});
|
||||
|
||||
describe("Token Operations", () => {
|
||||
const testToken: SmapiToken = {
|
||||
token: "test-jwt-token",
|
||||
key: "test-key-123",
|
||||
};
|
||||
|
||||
it("should set and get a token", () => {
|
||||
tokenStore.set("token1", testToken);
|
||||
const retrieved = tokenStore.get("token1");
|
||||
|
||||
expect(retrieved).toEqual(testToken);
|
||||
});
|
||||
|
||||
it("should return undefined for non-existent token", () => {
|
||||
const retrieved = tokenStore.get("non-existent");
|
||||
expect(retrieved).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should update existing token", () => {
|
||||
tokenStore.set("token1", testToken);
|
||||
|
||||
const updatedToken: SmapiToken = {
|
||||
token: "updated-jwt-token",
|
||||
key: "updated-key-456",
|
||||
};
|
||||
|
||||
tokenStore.set("token1", updatedToken);
|
||||
const retrieved = tokenStore.get("token1");
|
||||
|
||||
expect(retrieved).toEqual(updatedToken);
|
||||
});
|
||||
|
||||
it("should delete a token", () => {
|
||||
tokenStore.set("token1", testToken);
|
||||
tokenStore.delete("token1");
|
||||
const retrieved = tokenStore.get("token1");
|
||||
|
||||
expect(retrieved).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should get all tokens", () => {
|
||||
const token1: SmapiToken = { token: "jwt1", key: "key1" };
|
||||
const token2: SmapiToken = { token: "jwt2", key: "key2" };
|
||||
const token3: SmapiToken = { token: "jwt3", key: "key3" };
|
||||
|
||||
tokenStore.set("tokenKey1", token1);
|
||||
tokenStore.set("tokenKey2", token2);
|
||||
tokenStore.set("tokenKey3", token3);
|
||||
|
||||
const allTokens = tokenStore.getAll();
|
||||
|
||||
expect(Object.keys(allTokens).length).toBe(3);
|
||||
expect(allTokens["tokenKey1"]).toEqual(token1);
|
||||
expect(allTokens["tokenKey2"]).toEqual(token2);
|
||||
expect(allTokens["tokenKey3"]).toEqual(token3);
|
||||
});
|
||||
|
||||
it("should return empty object when no tokens exist", () => {
|
||||
const allTokens = tokenStore.getAll();
|
||||
expect(allTokens).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Token Cleanup", () => {
|
||||
it("should cleanup invalid tokens", () => {
|
||||
const smapiAuthTokens = new JWTSmapiLoginTokens(SystemClock, "test-secret", "1h");
|
||||
|
||||
// Create valid tokens
|
||||
const validToken1 = smapiAuthTokens.issue("service-token-1");
|
||||
const validToken2 = smapiAuthTokens.issue("service-token-2");
|
||||
|
||||
// Create invalid token (wrong secret)
|
||||
const invalidAuthTokens = new JWTSmapiLoginTokens(SystemClock, "different-secret", "1h");
|
||||
const invalidToken = invalidAuthTokens.issue("service-token-3");
|
||||
|
||||
tokenStore.set("valid1", validToken1);
|
||||
tokenStore.set("valid2", validToken2);
|
||||
tokenStore.set("invalid", invalidToken);
|
||||
|
||||
// Clean up
|
||||
const deletedCount = tokenStore.cleanupExpired(smapiAuthTokens);
|
||||
|
||||
expect(deletedCount).toBe(1);
|
||||
expect(tokenStore.get("valid1")).toBeDefined();
|
||||
expect(tokenStore.get("valid2")).toBeDefined();
|
||||
expect(tokenStore.get("invalid")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should not cleanup expired tokens that can be refreshed", () => {
|
||||
// Note: This test would require mocking time to create an expired token
|
||||
// For now, we just verify the function runs without error
|
||||
const smapiAuthTokens = new JWTSmapiLoginTokens(SystemClock, "test-secret", "1h");
|
||||
const validToken = smapiAuthTokens.issue("service-token-1");
|
||||
|
||||
tokenStore.set("token1", validToken);
|
||||
const deletedCount = tokenStore.cleanupExpired(smapiAuthTokens);
|
||||
|
||||
expect(deletedCount).toBe(0);
|
||||
expect(tokenStore.get("token1")).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("JSON Migration", () => {
|
||||
it("should migrate tokens from JSON file", () => {
|
||||
const jsonTokens = {
|
||||
token1: { token: "jwt1", key: "key1" },
|
||||
token2: { token: "jwt2", key: "key2" },
|
||||
token3: { token: "jwt3", key: "key3" },
|
||||
};
|
||||
|
||||
fs.writeFileSync(testJsonPath, JSON.stringify(jsonTokens, null, 2), "utf8");
|
||||
|
||||
const migratedCount = tokenStore.migrateFromJSON(testJsonPath);
|
||||
|
||||
expect(migratedCount).toBe(3);
|
||||
expect(tokenStore.get("token1")).toEqual(jsonTokens.token1);
|
||||
expect(tokenStore.get("token2")).toEqual(jsonTokens.token2);
|
||||
expect(tokenStore.get("token3")).toEqual(jsonTokens.token3);
|
||||
});
|
||||
|
||||
it("should create backup of original JSON file", () => {
|
||||
const jsonTokens = {
|
||||
token1: { token: "jwt1", key: "key1" },
|
||||
};
|
||||
|
||||
fs.writeFileSync(testJsonPath, JSON.stringify(jsonTokens, null, 2), "utf8");
|
||||
tokenStore.migrateFromJSON(testJsonPath);
|
||||
|
||||
expect(fs.existsSync(`${testJsonPath}.bak`)).toBe(true);
|
||||
expect(fs.existsSync(testJsonPath)).toBe(false);
|
||||
});
|
||||
|
||||
it("should return 0 when JSON file does not exist", () => {
|
||||
const migratedCount = tokenStore.migrateFromJSON(testJsonPath);
|
||||
expect(migratedCount).toBe(0);
|
||||
});
|
||||
|
||||
it("should handle empty JSON file", () => {
|
||||
fs.writeFileSync(testJsonPath, JSON.stringify({}), "utf8");
|
||||
const migratedCount = tokenStore.migrateFromJSON(testJsonPath);
|
||||
|
||||
expect(migratedCount).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Persistence", () => {
|
||||
it("should persist tokens across instances", () => {
|
||||
const testToken: SmapiToken = { token: "jwt1", key: "key1" };
|
||||
|
||||
tokenStore.set("token1", testToken);
|
||||
tokenStore.close();
|
||||
|
||||
// Create new instance with same database
|
||||
const newStore = new SQLiteSmapiTokenStore(testDbPath);
|
||||
const retrieved = newStore.get("token1");
|
||||
|
||||
expect(retrieved).toEqual(testToken);
|
||||
|
||||
newStore.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Close", () => {
|
||||
it("should close database connection without error", () => {
|
||||
expect(() => tokenStore.close()).not.toThrow();
|
||||
});
|
||||
|
||||
it("should handle multiple close calls gracefully", () => {
|
||||
tokenStore.close();
|
||||
// Second close should not throw
|
||||
expect(() => tokenStore.close()).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user