import { afterEach, describe, expect, it, vi } from "vitest";
import { ConnectionPool } from "./connection_pool.js";
describe("ConnectionPool", () => {
  afterEach(() => {
    vi.useRealTimers();
  });
  const makeConnectCb = () => {
    let n = 0;
    return vi.fn(async (_timeout) => `conn_${++n}`);
  };
  describe("basic operations", () => {
    it("should create and return a connection", async () => {
      const connections = [];
      const connectCb = vi.fn(async (_timeout) => {
        const conn3 = `conn_${connections.length}`;
        connections.push(conn3);
        return conn3;
      });
      const closeCb = vi.fn(async (_conn) => {
      });
      const pool = new ConnectionPool({
        connectCb,
        closeCb
      });
      const conn = await pool.get();
      expect(conn).toBe("conn_0");
      expect(connectCb).toHaveBeenCalledTimes(1);
      pool.put(conn);
      const conn2 = await pool.get();
      expect(conn2).toBe("conn_0");
      expect(connectCb).toHaveBeenCalledTimes(1);
    });
    it("should create new connection when none available", async () => {
      const connectCb = makeConnectCb();
      const closeCb = vi.fn(async (_conn) => {
      });
      const pool = new ConnectionPool({
        connectCb,
        closeCb
      });
      const conn1 = await pool.get();
      pool.put(conn1);
      const conn2 = await pool.get();
      expect(conn1).toBe(conn2);
      expect(connectCb).toHaveBeenCalledTimes(1);
    });
    it("should remove connection from pool", async () => {
      const connectCb = makeConnectCb();
      const closeCb = vi.fn(async (_conn) => {
      });
      const pool = new ConnectionPool({
        connectCb,
        closeCb
      });
      const conn = await pool.get();
      pool.put(conn);
      pool.remove(conn);
      const conn2 = await pool.get();
      expect(conn2).not.toBe(conn);
      expect(connectCb).toHaveBeenCalledTimes(2);
      expect(closeCb).toHaveBeenCalledTimes(1);
    });
  });
  describe("maxSessionDuration", () => {
    it("should expire connections after maxSessionDuration", async () => {
      vi.useFakeTimers();
      const connectCb = makeConnectCb();
      const closeCb = vi.fn(async (_conn) => {
      });
      const pool = new ConnectionPool({
        connectCb,
        closeCb,
        maxSessionDuration: 100
        // 100ms
      });
      const conn1 = await pool.get();
      pool.put(conn1);
      await vi.advanceTimersByTimeAsync(150);
      const conn2 = await pool.get();
      expect(conn2).not.toBe(conn1);
      expect(connectCb).toHaveBeenCalledTimes(2);
      expect(closeCb).toHaveBeenCalledTimes(1);
    });
    it("should refresh connection timestamp when markRefreshedOnGet is true", async () => {
      vi.useFakeTimers();
      const connectCb = makeConnectCb();
      const closeCb = vi.fn(async (_conn) => {
      });
      const pool = new ConnectionPool({
        connectCb,
        closeCb,
        maxSessionDuration: 200,
        // 200ms
        markRefreshedOnGet: true
      });
      const conn1 = await pool.get();
      pool.put(conn1);
      await vi.advanceTimersByTimeAsync(100);
      const conn2 = await pool.get();
      expect(conn2).toBe(conn1);
      pool.put(conn2);
      await vi.advanceTimersByTimeAsync(100);
      const conn3 = await pool.get();
      expect(conn3).toBe(conn1);
      expect(connectCb).toHaveBeenCalledTimes(1);
    });
  });
  describe("withConnection", () => {
    it("should return connection to pool on success", async () => {
      const connectCb = makeConnectCb();
      const closeCb = vi.fn(async (_conn) => {
      });
      const pool = new ConnectionPool({
        connectCb,
        closeCb
      });
      let capturedConn;
      await pool.withConnection(async (conn) => {
        capturedConn = conn;
        return "result";
      });
      const conn2 = await pool.get();
      expect(conn2).toBe(capturedConn);
      expect(connectCb).toHaveBeenCalledTimes(1);
    });
    it("should remove connection from pool on error", async () => {
      const connectCb = makeConnectCb();
      const closeCb = vi.fn(async (_conn) => {
      });
      const pool = new ConnectionPool({
        connectCb,
        closeCb
      });
      let capturedConn;
      try {
        await pool.withConnection(async (conn) => {
          capturedConn = conn;
          throw new Error("test error");
        });
      } catch (e) {
      }
      const conn2 = await pool.get();
      expect(conn2).not.toBe(capturedConn);
      expect(connectCb).toHaveBeenCalledTimes(2);
      expect(closeCb).toHaveBeenCalledTimes(1);
    });
    it("should handle abort signal", async () => {
      vi.useFakeTimers();
      const connectCb = makeConnectCb();
      const closeCb = vi.fn(async (_conn) => {
      });
      const pool = new ConnectionPool({
        connectCb,
        closeCb
      });
      const abortController = new AbortController();
      let capturedConn;
      const promise = pool.withConnection(
        async (conn) => {
          capturedConn = conn;
          await new Promise((resolve) => setTimeout(resolve, 1e3));
          return "result";
        },
        { signal: abortController.signal }
      );
      setTimeout(() => abortController.abort(), 10);
      const rejection = expect(promise).rejects.toThrow();
      await vi.advanceTimersByTimeAsync(10);
      await rejection;
      const conn2 = await pool.get();
      expect(conn2).not.toBe(capturedConn);
      expect(closeCb).toHaveBeenCalledTimes(1);
    });
  });
  describe("prewarm", () => {
    it("should create connection in background", async () => {
      vi.useFakeTimers();
      let n = 0;
      const connectCb = vi.fn(async (_timeout) => {
        await new Promise((resolve) => setTimeout(resolve, 50));
        return `conn_${++n}`;
      });
      const closeCb = vi.fn(async (_conn) => {
      });
      const pool = new ConnectionPool({
        connectCb,
        closeCb
      });
      pool.prewarm();
      await vi.advanceTimersByTimeAsync(50);
      const conn = await pool.get();
      expect(conn).toBeDefined();
      expect(connectCb).toHaveBeenCalledTimes(1);
    });
    it("should not prewarm if connections already exist", async () => {
      const connectCb = makeConnectCb();
      const closeCb = vi.fn(async (_conn) => {
      });
      const pool = new ConnectionPool({
        connectCb,
        closeCb
      });
      const conn1 = await pool.get();
      pool.put(conn1);
      pool.prewarm();
      const conn2 = await pool.get();
      expect(conn2).toBe(conn1);
      expect(connectCb).toHaveBeenCalledTimes(1);
    });
  });
  describe("close", () => {
    it("should close all connections", async () => {
      const connectCb = makeConnectCb();
      const closeCb = vi.fn(async (_conn) => {
      });
      const pool = new ConnectionPool({
        connectCb,
        closeCb
      });
      const conn1 = await pool.get();
      const conn2 = await pool.get();
      pool.put(conn1);
      pool.put(conn2);
      await pool.close();
      expect(closeCb).toHaveBeenCalledTimes(2);
    });
    it("should invalidate all connections", async () => {
      const connectCb = makeConnectCb();
      const closeCb = vi.fn(async (_conn) => {
      });
      const pool = new ConnectionPool({
        connectCb,
        closeCb
      });
      const conn1 = await pool.get();
      const conn2 = await pool.get();
      pool.put(conn1);
      pool.put(conn2);
      pool.invalidate();
      await pool.close();
      expect(closeCb).toHaveBeenCalledTimes(2);
    });
  });
  describe("concurrent access", () => {
    it("should handle concurrent get requests", async () => {
      const connectCb = vi.fn(async (_timeout) => {
        await new Promise((resolve) => setTimeout(resolve, 10));
        return `conn_${Date.now()}_${Math.random()}`;
      });
      const closeCb = vi.fn(async (_conn) => {
      });
      const pool = new ConnectionPool({
        connectCb,
        closeCb
      });
      const promises = Array.from({ length: 5 }, () => pool.get());
      const connections = await Promise.all(promises);
      const uniqueConnections = new Set(connections);
      expect(uniqueConnections.size).toBe(5);
      expect(connectCb).toHaveBeenCalledTimes(5);
    });
  });
});
//# sourceMappingURL=connection_pool.test.js.map