import { test } from "node:test";
import assert from "node:assert/strict";
import { foiRoubada, travaQueNaoQuebra } from "./trava-do-login.ts";

/**
 * A trava do login roubada no meio do caminho.
 *
 * No navegador isso só aparece com aba lenta ou várias abas abertas, e quem
 * via era o assinante ("Pedido não encontrado", orçamento sem salvar). Aqui a
 * trava falsa reproduz exatamente o que a Web Locks faz quando outra
 * operação rouba a trava: a promessa de quem a tinha rejeita com AbortError,
 * mas o trabalho dele continua até o fim.
 */

function erroDeRoubo() {
  const erro = new Error("Lock broken by another request with the 'steal' option.");
  erro.name = "AbortError";
  return erro;
}

test("reconhece o erro de trava roubada e só ele", () => {
  assert.equal(foiRoubada(erroDeRoubo()), true);
  const outroAbort = new Error("The operation was aborted.");
  outroAbort.name = "AbortError";
  assert.equal(foiRoubada(outroAbort), false);
  assert.equal(foiRoubada(new Error("Lock broken by another request with the 'steal' option.")), false);
  assert.equal(foiRoubada(null), false);
});

test("trava roubada: devolve o resultado do que já estava rodando, sem rodar de novo", async () => {
  let vezes = 0;
  const roubaNoMeio = async <R,>(_nome: string, _espera: number, fazer: () => Promise<R>): Promise<R> => {
    void fazer();
    throw erroDeRoubo();
  };
  const trava = travaQueNaoQuebra(roubaNoMeio);
  const resultado = await trava("lock:sb", 5000, async () => {
    vezes += 1;
    await new Promise((r) => setTimeout(r, 5));
    return "sessão";
  });
  assert.equal(resultado, "sessão");
  assert.equal(vezes, 1);
});

test("sem roubo: igual à trava original", async () => {
  const normal = async <R,>(_nome: string, _espera: number, fazer: () => Promise<R>) => fazer();
  assert.equal(await travaQueNaoQuebra(normal)("lock:sb", 5000, async () => 42), 42);
});

test("outros erros continuam subindo", async () => {
  const falha = async () => {
    throw new Error("rede caiu");
  };
  await assert.rejects(travaQueNaoQuebra(falha)("lock:sb", 5000, async () => 1), /rede caiu/);
  const roubadaAntesDeComecar = async () => {
    throw erroDeRoubo();
  };
  await assert.rejects(travaQueNaoQuebra(roubadaAntesDeComecar)("lock:sb", 5000, async () => 1), /steal/);
});

test("o trabalho que falha depois do roubo leva o erro dele, não o do roubo", async () => {
  const roubaNoMeio = async <R,>(_n: string, _e: number, fazer: () => Promise<R>): Promise<R> => {
    void fazer().catch(() => undefined);
    throw erroDeRoubo();
  };
  await assert.rejects(
    travaQueNaoQuebra(roubaNoMeio)("lock:sb", 5000, async () => {
      throw new Error("token inválido");
    }),
    /token inválido/
  );
});
