import assert from "node:assert/strict"; import { randomUUID } from "node:crypto"; import test from "node:test"; import { SequelizeTransactionManager } from "@erp/core/api"; import { Name, UniqueID } from "@repo/rdx-ddd"; import { Result } from "@repo/rdx-utils"; import { AuditChangeModel, AuditEventModel } from "@/infrastructure/audit/sequelize"; import { PaymentMethodUpdater } from "../../../../../modules/catalogs/src/api/application/payment-methods/services/payment-method-updater"; import { SequelizePaymentMethodDomainMapper } from "../../../../../modules/catalogs/src/api/infrastructure/payment-methods/persistence/sequelize/mappers/sequelize-payment-method-domain.mapper"; import { SequelizePaymentMethodSummaryMapper } from "../../../../../modules/catalogs/src/api/infrastructure/payment-methods/persistence/sequelize/mappers/sequelize-payment-method-summary.mapper"; import { PaymentMethodModel } from "../../../../../modules/catalogs/src/api/infrastructure/payment-methods/persistence/sequelize/models/sequelize-payment-method.model"; import { SequelizePaymentMethodRepository } from "../../../../../modules/catalogs/src/api/infrastructure/payment-methods/persistence/sequelize/repositories/sequelize-payment-method.repository"; import { buildCompanyHeaders, createTestAccount, createTestCompany, createTestMembership, loginByApi, } from "./http-test-fixtures"; import { createHttpTestRuntime } from "./http-test-runtime"; test("payment methods registra create y update con el contexto HTTP en MariaDB", async (t) => { const runtime = await createHttpTestRuntime(); t.after(async () => runtime.stop()); const account = await createTestAccount(runtime.database, { email: `payment-method-audit-${randomUUID()}@example.test`, password: "Password123!", }); const company = await createTestCompany(runtime.database, { legalName: "Payment method audit company", }); await createTestMembership(runtime.database, { accountId: account.id, companyId: company.id, }); const loginResponse = await loginByApi({ request: runtime.request, email: account.email, password: account.password, }); assert.equal(loginResponse.status, 200); const accessToken = (loginResponse.body as { access_token: string }).access_token; const paymentMethodId = randomUUID(); const createResponse = await runtime.request("/api/v1/catalogs/payment-methods", { method: "POST", headers: { ...buildCompanyHeaders({ accessToken, companyId: company.id }), "content-type": "application/json", "x-correlation-id": "payment-method-create-correlation", }, body: JSON.stringify({ id: paymentMethodId, name: "Cash", description: "Counter payment", is_active: true, }), }); assert.equal(createResponse.status, 201); const createdAudit = await AuditEventModel.findOne({ where: { action: "payment_method.created", target_id: paymentMethodId }, }); assert.ok(createdAudit); assert.equal(createdAudit.source_type, "application"); assert.equal(createdAudit.source_id, "uecko-erp"); assert.equal(createdAudit.origin, "http"); assert.equal(createdAudit.operation_id, createResponse.headers.get("x-request-id")); assert.equal(createdAudit.correlation_id, "payment-method-create-correlation"); assert.equal(createdAudit.actor_type, "user"); assert.equal(createdAudit.actor_id, account.id); assert.equal(createdAudit.scope_type, "company"); assert.equal(createdAudit.scope_id, company.id); assert.equal(createdAudit.target_type, "payment_method"); assert.equal(createdAudit.target_label, "Cash"); const updateResponse = await runtime.request( `/api/v1/catalogs/payment-methods/${paymentMethodId}`, { method: "PUT", headers: { ...buildCompanyHeaders({ accessToken, companyId: company.id }), "content-type": "application/json", "x-correlation-id": "payment-method-update-correlation", }, body: JSON.stringify({ name: "Cash updated" }), } ); assert.equal(updateResponse.status, 200); const updatedAudit = await AuditEventModel.findOne({ where: { action: "payment_method.updated", target_id: paymentMethodId }, }); assert.ok(updatedAudit); assert.equal(updatedAudit.target_label, "Cash updated"); assert.equal(updatedAudit.actor_id, account.id); assert.equal(updatedAudit.scope_id, company.id); const changes = await AuditChangeModel.findAll({ where: { audit_event_id: updatedAudit.id }, order: [["position", "ASC"]], }); assert.deepEqual( changes.map((change) => ({ path: change.path, kind: change.kind, before: change.before_value, after: change.after_value, })), [{ path: "name", kind: "change", before: "Cash", after: "Cash updated" }] ); const secondPaymentMethodId = randomUUID(); const secondCreateResponse = await runtime.request("/api/v1/catalogs/payment-methods", { method: "POST", headers: { ...buildCompanyHeaders({ accessToken, companyId: company.id }), "content-type": "application/json", }, body: JSON.stringify({ id: secondPaymentMethodId, name: "Card", is_active: true, }), }); assert.equal(secondCreateResponse.status, 201); const firstDefaultResponse = await runtime.request( `/api/v1/catalogs/payment-methods/${paymentMethodId}/default`, { method: "PATCH", headers: buildCompanyHeaders({ accessToken, companyId: company.id }), } ); assert.equal(firstDefaultResponse.status, 200); const defaultResponse = await runtime.request( `/api/v1/catalogs/payment-methods/${secondPaymentMethodId}/default`, { method: "PATCH", headers: { ...buildCompanyHeaders({ accessToken, companyId: company.id }), "x-correlation-id": "payment-method-default-correlation", }, } ); assert.equal(defaultResponse.status, 200); const defaultEvents = await AuditEventModel.findAll({ where: { action: ["payment_method.default_unset", "payment_method.default_set"], operation_id: defaultResponse.headers.get("x-request-id") ?? "", }, order: [["action", "ASC"]], }); assert.equal(defaultEvents.length, 2); const unsetEvent = defaultEvents.find((entry) => entry.action === "payment_method.default_unset"); const setEvent = defaultEvents.find((entry) => entry.action === "payment_method.default_set"); assert.equal(unsetEvent?.target_id, paymentMethodId); assert.equal(setEvent?.target_id, secondPaymentMethodId); assert.equal(unsetEvent?.correlation_id, "payment-method-default-correlation"); assert.equal(setEvent?.correlation_id, "payment-method-default-correlation"); assert.equal(unsetEvent?.actor_id, account.id); assert.equal(setEvent?.scope_id, company.id); const [unsetChange] = await AuditChangeModel.findAll({ where: { audit_event_id: unsetEvent?.id ?? "" }, }); const [setChange] = await AuditChangeModel.findAll({ where: { audit_event_id: setEvent?.id ?? "" }, }); assert.deepEqual( { path: unsetChange?.path, before: unsetChange?.before_value, after: unsetChange?.after_value }, { path: "isDefault", before: true, after: false } ); assert.deepEqual( { path: setChange?.path, before: setChange?.before_value, after: setChange?.after_value }, { path: "isDefault", before: false, after: true } ); const originalName = "Cash updated"; const rollbackName = "Cash rollback attempt"; const repository = new SequelizePaymentMethodRepository( new SequelizePaymentMethodDomainMapper(), new SequelizePaymentMethodSummaryMapper(), runtime.database ); const updater = new PaymentMethodUpdater(repository, runtime.audit); const transactionManager = new SequelizeTransactionManager( runtime.database, runtime.transactionContext ); const paymentMethodIdResult = UniqueID.create(paymentMethodId); const rollbackNameResult = Name.create(rollbackName); assert.equal(paymentMethodIdResult.isSuccess, true); assert.equal(rollbackNameResult.isSuccess, true); const changesBeforeRollback = await AuditChangeModel.count(); await assert.rejects( () => runtime.operationContext.run( { operationId: "payment-method-rollback-operation", correlationId: "payment-method-rollback-correlation", origin: "http", actor: { type: "user", id: account.id, label: account.email }, scope: { type: "company", id: company.id, label: null }, }, () => transactionManager.complete(async (transaction) => { const updateResult = await updater.update({ companyId: UniqueID.create(company.id).data, id: paymentMethodIdResult.data, patchProps: { name: rollbackNameResult.data }, transaction, }); assert.equal(updateResult.isSuccess, true); return Result.fail(new Error("forced payment method rollback")); }) ), /forced payment method rollback/ ); const rolledBackPaymentMethod = await PaymentMethodModel.findByPk(paymentMethodId); assert.equal(rolledBackPaymentMethod?.name, originalName); assert.equal( await AuditEventModel.count({ where: { operation_id: "payment-method-rollback-operation" }, }), 0 ); assert.equal(await AuditChangeModel.count(), changesBeforeRollback); });