/**
 * Tests for server/middleware/verifyCsrf.js
 *
 * CSRF protection via the double-submit cookie pattern. These tests verify
 * that the timing-safe comparison works correctly and that safe HTTP methods
 * are correctly exempted.
 */

import verifyCsrf from '../../server/middleware/verifyCsrf.js';

function makeReq(method, cookieToken, headerToken) {
  return {
    method,
    cookies: { csrf_token: cookieToken },
    headers: { 'x-csrf-token': headerToken }
  };
}

function makeRes() {
  const res = {
    statusCode: null,
    body: null,
    status(code) { this.statusCode = code; return this; },
    json(body) { this.body = body; return this; }
  };
  return res;
}

describe('verifyCsrf middleware', () => {
  describe('safe methods are exempted', () => {
    for (const method of ['GET', 'HEAD', 'OPTIONS']) {
      it(`allows ${method} with no CSRF token`, () => {
        const next = vi.fn();
        verifyCsrf(makeReq(method, undefined, undefined), makeRes(), next);
        expect(next).toHaveBeenCalledOnce();
      });
    }
  });

  describe('mutating methods require valid token', () => {
    const TOKEN = 'a'.repeat(64); // 64 hex chars = 32 bytes

    for (const method of ['POST', 'PUT', 'PATCH', 'DELETE']) {
      it(`allows ${method} with matching tokens`, () => {
        const next = vi.fn();
        const res = makeRes();
        verifyCsrf(makeReq(method, TOKEN, TOKEN), res, next);
        expect(next).toHaveBeenCalledOnce();
        expect(res.statusCode).toBeNull();
      });

      it(`blocks ${method} with mismatched tokens`, () => {
        const next = vi.fn();
        const res = makeRes();
        verifyCsrf(makeReq(method, TOKEN, TOKEN.replace('a', 'b')), res, next);
        expect(res.statusCode).toBe(403);
        expect(next).not.toHaveBeenCalled();
      });

      it(`blocks ${method} with missing cookie token`, () => {
        const next = vi.fn();
        const res = makeRes();
        verifyCsrf(makeReq(method, undefined, TOKEN), res, next);
        expect(res.statusCode).toBe(403);
        expect(res.body.error).toBe('CSRF token missing');
      });

      it(`blocks ${method} with missing header token`, () => {
        const next = vi.fn();
        const res = makeRes();
        verifyCsrf(makeReq(method, TOKEN, undefined), res, next);
        expect(res.statusCode).toBe(403);
      });
    }

    it('blocks when tokens have different lengths (timing-safe path)', () => {
      const next = vi.fn();
      const res = makeRes();
      verifyCsrf(makeReq('POST', 'short', 'much-longer-token-here'), res, next);
      expect(res.statusCode).toBe(403);
      expect(res.body.error).toBe('CSRF token invalid');
    });
  });
});
