A Deno-compatible AT Protocol OAuth client that serves as a drop-in replacement for @atproto/oauth-client-node
1/**
2 * @fileoverview Tests for storage implementations
3 */
4
5import { assertEquals } from "@std/assert";
6import { LocalStorage, MemoryStorage } from "../src/storage.ts";
7
8// Mock localStorage for testing
9class MockLocalStorage {
10 private store = new Map<string, string>();
11
12 getItem(key: string): string | null {
13 return this.store.get(key) ?? null;
14 }
15
16 setItem(key: string, value: string): void {
17 this.store.set(key, value);
18 }
19
20 removeItem(key: string): void {
21 this.store.delete(key);
22 }
23
24 clear(): void {
25 this.store.clear();
26 }
27}
28
29// Setup mock for LocalStorage tests
30const mockLocalStorage = new MockLocalStorage();
31// @ts-ignore: Mock global for testing
32globalThis.localStorage = mockLocalStorage;
33
34Deno.test("MemoryStorage - Basic Operations", async (t) => {
35 const storage = new MemoryStorage();
36
37 await t.step("should store and retrieve values", async () => {
38 await storage.set("test-key", { data: "test-value" });
39 const result = await storage.get<{ data: string }>("test-key");
40 assertEquals(result, { data: "test-value" });
41 });
42
43 await t.step("should return null for non-existent keys", async () => {
44 const result = await storage.get("non-existent");
45 assertEquals(result, null);
46 });
47
48 await t.step("should delete values", async () => {
49 await storage.set("delete-me", "test");
50 await storage.delete("delete-me");
51 const result = await storage.get("delete-me");
52 assertEquals(result, null);
53 });
54
55 await t.step("should clear all values", async () => {
56 await storage.set("key1", "value1");
57 await storage.set("key2", "value2");
58 storage.clear();
59 assertEquals(await storage.get("key1"), null);
60 assertEquals(await storage.get("key2"), null);
61 });
62});
63
64Deno.test("MemoryStorage - TTL Functionality", async (t) => {
65 const storage = new MemoryStorage();
66
67 await t.step("should store values with TTL", async () => {
68 await storage.set("ttl-key", "ttl-value", { ttl: 1 }); // 1 second TTL
69 const result = await storage.get("ttl-key");
70 assertEquals(result, "ttl-value");
71 });
72
73 await t.step("should expire values after TTL", async () => {
74 await storage.set("expire-key", "expire-value", { ttl: 0.1 }); // 100ms TTL
75
76 // Should exist immediately
77 const beforeExpiry = await storage.get("expire-key");
78 assertEquals(beforeExpiry, "expire-value");
79
80 // Wait for expiry and check
81 await new Promise((resolve) => setTimeout(resolve, 150));
82 const afterExpiry = await storage.get("expire-key");
83 assertEquals(afterExpiry, null);
84 });
85
86 await t.step("should store values without TTL indefinitely", async () => {
87 await storage.set("no-ttl", "persistent");
88
89 // Wait a bit and verify it's still there
90 await new Promise((resolve) => setTimeout(resolve, 50));
91 const result = await storage.get("no-ttl");
92 assertEquals(result, "persistent");
93 });
94});
95
96Deno.test("MemoryStorage - Data Types", async (t) => {
97 const storage = new MemoryStorage();
98
99 await t.step("should handle strings", async () => {
100 await storage.set("string", "test-string");
101 assertEquals(await storage.get("string"), "test-string");
102 });
103
104 await t.step("should handle numbers", async () => {
105 await storage.set("number", 42);
106 assertEquals(await storage.get("number"), 42);
107 });
108
109 await t.step("should handle objects", async () => {
110 const obj = { key: "value", nested: { data: 123 } };
111 await storage.set("object", obj);
112 assertEquals(await storage.get("object"), obj);
113 });
114
115 await t.step("should handle arrays", async () => {
116 const arr = [1, "two", { three: 3 }];
117 await storage.set("array", arr);
118 assertEquals(await storage.get("array"), arr);
119 });
120
121 await t.step("should handle booleans", async () => {
122 await storage.set("bool-true", true);
123 await storage.set("bool-false", false);
124 assertEquals(await storage.get("bool-true"), true);
125 assertEquals(await storage.get("bool-false"), false);
126 });
127});
128
129Deno.test("LocalStorage - Basic Operations", async (t) => {
130 const storage = new LocalStorage();
131
132 // Clear mock storage before each test
133 mockLocalStorage.clear();
134
135 await t.step("should store and retrieve values", async () => {
136 await storage.set("local-key", { data: "local-value" });
137 const result = await storage.get<{ data: string }>("local-key");
138 assertEquals(result, { data: "local-value" });
139 });
140
141 await t.step("should return null for non-existent keys", async () => {
142 const result = await storage.get("non-existent");
143 assertEquals(result, null);
144 });
145
146 await t.step("should delete values", async () => {
147 await storage.set("delete-local", "test");
148 await storage.delete("delete-local");
149 const result = await storage.get("delete-local");
150 assertEquals(result, null);
151 });
152});
153
154Deno.test("LocalStorage - TTL Functionality", async (t) => {
155 const storage = new LocalStorage();
156 mockLocalStorage.clear();
157
158 await t.step("should store values with TTL", async () => {
159 await storage.set("ttl-local", "ttl-value", { ttl: 1 });
160 const result = await storage.get("ttl-local");
161 assertEquals(result, "ttl-value");
162 });
163
164 await t.step("should expire values after TTL", async () => {
165 await storage.set("expire-local", "expire-value", { ttl: 0.1 }); // 100ms TTL
166
167 // Should exist immediately
168 const beforeExpiry = await storage.get("expire-local");
169 assertEquals(beforeExpiry, "expire-value");
170
171 // Wait for expiry and check
172 await new Promise((resolve) => setTimeout(resolve, 150));
173 const afterExpiry = await storage.get("expire-local");
174 assertEquals(afterExpiry, null);
175 });
176});
177
178Deno.test("LocalStorage - Error Handling", async (t) => {
179 const storage = new LocalStorage();
180 mockLocalStorage.clear();
181
182 await t.step("should handle JSON parse errors gracefully", async () => {
183 // Manually set invalid JSON in mock storage
184 mockLocalStorage.setItem("invalid-json", "invalid-json-data");
185
186 const result = await storage.get("invalid-json");
187 assertEquals(result, null);
188 });
189});