const { describe, it, after, beforeEach } = require('node:test'); const assert = require('node:assert'); const fs = require('fs'); const path = require('path'); // Setup must be required first const { cleanTestData, testDataDir } = require('./setup'); // Now require storage (after env vars are set) const storage = require('../src/storage'); const config = require('../src/config'); describe('Storage', () => { beforeEach(() => { cleanTestData(); storage.ensureDirectories(); }); after(() => { cleanTestData(); }); describe('Auth Requests', () => { it('should create and retrieve an auth request', () => { const requestId = 'test-request-123'; const email = 'test@example.com'; const created = storage.createAuthRequest(requestId, email); assert.strictEqual(created.request_id, requestId); assert.strictEqual(created.email, email); assert.strictEqual(created.status, 'pending'); assert.strictEqual(created.session_token, null); const retrieved = storage.getAuthRequest(requestId); assert.strictEqual(retrieved.request_id, requestId); assert.strictEqual(retrieved.email, email); }); it('should update an auth request', () => { const requestId = 'test-request-456'; storage.createAuthRequest(requestId, 'test@example.com'); const updated = storage.updateAuthRequest(requestId, { status: 'verified', session_token: 'fake-token', }); assert.strictEqual(updated.status, 'verified'); assert.strictEqual(updated.session_token, 'fake-token'); const retrieved = storage.getAuthRequest(requestId); assert.strictEqual(retrieved.status, 'verified'); }); it('should delete an auth request', () => { const requestId = 'test-request-789'; storage.createAuthRequest(requestId, 'test@example.com'); const deleted = storage.deleteAuthRequest(requestId); assert.strictEqual(deleted, true); const retrieved = storage.getAuthRequest(requestId); assert.strictEqual(retrieved, null); }); it('should return null for non-existent request', () => { const retrieved = storage.getAuthRequest('non-existent'); assert.strictEqual(retrieved, null); }); it('should return null for expired request', () => { const requestId = 'expired-request'; const email = 'test@example.com'; storage.createAuthRequest(requestId, email); // Backdate via direct SQL const oldTimestamp = Date.now() - config.REQUEST_ID_EXPIRY_MS - 1000; storage.getDb().prepare('UPDATE auth_requests SET created_at = ? WHERE request_id = ?').run(oldTimestamp, requestId); const retrieved = storage.getAuthRequest(requestId); assert.strictEqual(retrieved, null); }); }); describe('Rate Limiting', () => { it('should allow requests under limit', () => { const email = 'ratelimit@example.com'; for (let i = 0; i < config.RATE_LIMIT_MAX_REQUESTS; i++) { const result = storage.checkRateLimit(email); assert.strictEqual(result.allowed, true); } }); it('should block requests over limit', () => { const email = 'overlimit@example.com'; // Use up all allowed requests for (let i = 0; i < config.RATE_LIMIT_MAX_REQUESTS; i++) { storage.checkRateLimit(email); } // Next should be blocked const result = storage.checkRateLimit(email); assert.strictEqual(result.allowed, false); assert.ok(result.resetTime > Date.now()); }); it('should track remaining requests', () => { const email = 'remaining@example.com'; const first = storage.checkRateLimit(email); assert.strictEqual(first.remaining, config.RATE_LIMIT_MAX_REQUESTS - 1); const second = storage.checkRateLimit(email); assert.strictEqual(second.remaining, config.RATE_LIMIT_MAX_REQUESTS - 2); }); }); describe('Grandfathered Emails', () => { it('should read grandfathered emails from file', () => { const grandfatheredFile = path.join(testDataDir, config.GRANDFATHERED_FILE); fs.writeFileSync(grandfatheredFile, 'donor1@example.com\nDonor2@Example.com\n'); storage.loadGrandfatheredEmails(); assert.strictEqual(storage.isGrandfathered('donor1@example.com'), true); assert.strictEqual(storage.isGrandfathered('DONOR1@EXAMPLE.COM'), true); assert.strictEqual(storage.isGrandfathered('donor2@example.com'), true); assert.strictEqual(storage.isGrandfathered('notadonor@example.com'), false); }); it('should handle missing grandfathered file gracefully', () => { storage.loadGrandfatheredEmails(); // File doesn't exist, should not throw assert.strictEqual(storage.isGrandfathered('anyone@example.com'), false); }); }); describe('Subscription Cache', () => { it('should store and retrieve subscription status', () => { storage.setSubscriptionStatus('sub@example.com', true, 'cus_123'); const status = storage.getSubscriptionStatus('sub@example.com'); assert.strictEqual(status.premium, true); assert.strictEqual(status.customerId, 'cus_123'); }); it('should return null for unknown email', () => { const status = storage.getSubscriptionStatus('unknown@example.com'); assert.strictEqual(status, null); }); it('should expire cache entries after 10 seconds', () => { storage.setSubscriptionStatus('ttl@example.com', false, null); // Backdate via direct SQL const oldTime = Date.now() - 11000; storage.getDb().prepare('UPDATE subscription_cache SET updated_at = ? WHERE email = ?').run(oldTime, 'ttl@example.com'); const status = storage.getSubscriptionStatus('ttl@example.com'); assert.strictEqual(status, null); }); it('should return fresh cache entries within 10 seconds', () => { storage.setSubscriptionStatus('fresh@example.com', false, null); const status = storage.getSubscriptionStatus('fresh@example.com'); assert.strictEqual(status.premium, false); }); }); describe('Pruning', () => { it('should prune expired auth requests', () => { // Create an expired request via createAuthRequest + backdate storage.createAuthRequest('old-request', 'test@example.com'); const oldTimestamp = Date.now() - config.REQUEST_ID_EXPIRY_MS - 1000; storage.getDb().prepare('UPDATE auth_requests SET created_at = ? WHERE request_id = ?').run(oldTimestamp, 'old-request'); // Create a valid request storage.createAuthRequest('valid-request', 'test@example.com'); const pruned = storage.pruneExpiredAuthRequests(); assert.strictEqual(pruned, 1); assert.strictEqual(storage.getAuthRequest('old-request'), null); assert.notStrictEqual(storage.getAuthRequest('valid-request'), null); }); }); });