import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { createRateLimiter, resolveUpstashCredentials } from '../../security/rate-limit.js';
describe('resolveUpstashCredentials', () => {
    it('returns undefined when nothing is configured', () => {
        expect(resolveUpstashCredentials({})).toEqual({ url: undefined, token: undefined });
    });
    it('reads the native UPSTASH_* names', () => {
        expect(resolveUpstashCredentials({
            UPSTASH_REDIS_REST_URL: 'https://u.upstash.io',
            UPSTASH_REDIS_REST_TOKEN: 'tok',
        })).toEqual({ url: 'https://u.upstash.io', token: 'tok' });
    });
    it('falls back to the Vercel Marketplace KV_REST_API_* names', () => {
        // Vercel's Upstash integration provisions these instead of UPSTASH_*.
        expect(resolveUpstashCredentials({
            KV_REST_API_URL: 'https://kv.upstash.io',
            KV_REST_API_TOKEN: 'kv-tok',
        })).toEqual({ url: 'https://kv.upstash.io', token: 'kv-tok' });
    });
    it('prefers UPSTASH_* over KV_* when both are present', () => {
        expect(resolveUpstashCredentials({
            UPSTASH_REDIS_REST_URL: 'https://native.upstash.io',
            UPSTASH_REDIS_REST_TOKEN: 'native',
            KV_REST_API_URL: 'https://kv.upstash.io',
            KV_REST_API_TOKEN: 'kv',
        })).toEqual({ url: 'https://native.upstash.io', token: 'native' });
    });
});
describe('createRateLimiter', () => {
    beforeEach(() => {
        vi.useFakeTimers();
    });
    afterEach(() => {
        vi.useRealTimers();
    });
    it('allows requests under the limit', async () => {
        const limiter = createRateLimiter({ windowMs: 60_000, maxRequests: 5 });
        const r1 = await limiter.check('ip:127.0.0.1');
        expect(r1.allowed).toBe(true);
        expect(r1.remaining).toBe(4);
        const r2 = await limiter.check('ip:127.0.0.1');
        expect(r2.allowed).toBe(true);
        expect(r2.remaining).toBe(3);
    });
    it('blocks requests over the limit', async () => {
        const limiter = createRateLimiter({ windowMs: 60_000, maxRequests: 3 });
        await limiter.check('ip:test');
        await limiter.check('ip:test');
        await limiter.check('ip:test');
        const blocked = await limiter.check('ip:test');
        expect(blocked.allowed).toBe(false);
        expect(blocked.remaining).toBe(0);
        expect(blocked.retryAfter).toBeDefined();
        expect(blocked.retryAfter).toBeGreaterThan(0);
    });
    it('resets after window expires', async () => {
        const limiter = createRateLimiter({ windowMs: 10_000, maxRequests: 2 });
        await limiter.check('ip:expire');
        await limiter.check('ip:expire');
        const blocked = await limiter.check('ip:expire');
        expect(blocked.allowed).toBe(false);
        vi.advanceTimersByTime(11_000);
        const afterReset = await limiter.check('ip:expire');
        expect(afterReset.allowed).toBe(true);
        expect(afterReset.remaining).toBe(1);
    });
    it('reset() clears the counter', async () => {
        const limiter = createRateLimiter({ windowMs: 60_000, maxRequests: 2 });
        await limiter.check('ip:reset');
        await limiter.check('ip:reset');
        const blocked = await limiter.check('ip:reset');
        expect(blocked.allowed).toBe(false);
        await limiter.reset('ip:reset');
        const afterReset = await limiter.check('ip:reset');
        expect(afterReset.allowed).toBe(true);
        expect(afterReset.remaining).toBe(1);
    });
    it('tracks different keys independently', async () => {
        const limiter = createRateLimiter({ windowMs: 60_000, maxRequests: 1 });
        const r1 = await limiter.check('key-a');
        expect(r1.allowed).toBe(true);
        const r2 = await limiter.check('key-b');
        expect(r2.allowed).toBe(true);
        const r3 = await limiter.check('key-a');
        expect(r3.allowed).toBe(false);
    });
    it('caps the in-memory key map and evicts the oldest (LRU)', async () => {
        // M1: with maxKeys=3, after seeing keys a, b, c, then d, the LRU entry
        // (a) must have been evicted. We verify by re-checking 'a' — its counter
        // should restart from 1 (i.e. no prior history).
        const limiter = createRateLimiter({
            windowMs: 60_000,
            maxRequests: 5,
            maxKeys: 3,
        });
        // Key 'a' gets two hits (count = 2)
        await limiter.check('a');
        await limiter.check('a');
        // Keys 'b', 'c', 'd' get one hit each. Inserting 'd' must evict 'a'
        // because 'a' is now the least-recently-used (oldest insertion).
        await limiter.check('b');
        await limiter.check('c');
        await limiter.check('d');
        // 'a' should look fresh — its prior count was evicted.
        const a = await limiter.check('a');
        expect(a.remaining).toBe(4); // 5 max, 1 used = 4 remaining
    });
    it('marks recently-touched keys as MRU so they are not evicted first', async () => {
        // M1: re-checking 'a' between inserts moves it to the tail of the LRU
        // map. With maxKeys=3, inserting 'd' must then evict 'b' (now the
        // oldest), not 'a'.
        const limiter = createRateLimiter({
            windowMs: 60_000,
            maxRequests: 5,
            maxKeys: 3,
        });
        await limiter.check('a');
        await limiter.check('b');
        await limiter.check('c');
        await limiter.check('a'); // touches 'a' → most-recent
        await limiter.check('d'); // evicts 'b' (oldest)
        const a = await limiter.check('a');
        // 'a' had 2 prior hits and now this 3rd one
        expect(a.remaining).toBe(2);
        const b = await limiter.check('b');
        // 'b' was evicted, restarted
        expect(b.remaining).toBe(4);
    });
});
//# sourceMappingURL=rate-limit.test.js.map