test/unit/api/v2/assets/assets.controller.spec.tsmodified
import sinon from "sinon";
import * as chai from "chai";
import chaiAsPromised from "chai-as-promised";
import sinonChai from "sinon-chai";
import { mockRequest, mockResponse, RequestOutput, ResponseOutput } from "mock-req-res";
import * as service from "../../../../../src/services/assets.service";
import * as controller from "../../../../../src/api/v2/projects/assets/assets.controller";
import { NotFoundError } from "../../../../../src/types/errortypes";
import { Asset } from "../../../../../src/services/assets.service";

chai.use(chaiAsPromised).use(sinonChai);
chai.should();
const expect = chai.expect;

/* eslint-disable @typescript-eslint/no-explicit-any */
describe("Assets Controller", () => {
    let req: RequestOutput;
    let res: ResponseOutput;
    const projectId = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
    const assetId = "11111111-2222-3333-4444-555555555555";
    const assetTypeId = "cccccccc-dddd-eeee-ffff-aaaaaaaaaaaa";

    const sampleAsset: Asset = {
        assetId,
        name: "Chiller Unit 1",
        assetTypeId,
        createdBy: "user@example.com",
        insertedOn: new Date("2025-01-01T00:00:00Z"),
        lastModifiedOn: null,
        lastModifiedBy: null,
    };

    beforeEach(() => {
        res = mockResponse();
    });

    afterEach(() => {
        sinon.restore();
    });

    describe("listAssets", () => {
        it("should return 200 with a paginated envelope of assets", async () => {
            req = mockRequest({ params: { projectId }, query: {} });
            sinon.stub(service, "listAssets").resolves([{ ...sampleAsset, indexId: 42 }]);

            await controller.listAssets(req as any, res as any);

            expect(res.status).to.have.been.calledWith(200);
            expect(res.json).to.have.been.calledWith({
                records: [sampleAsset],
                recordCount: 1,
                lastFetchedIndexId: 42,
            });
        });

        it("should forward paging query params to the service", async () => {
            req = mockRequest({ params: { projectId }, query: { lastFetchedIndexId: "42", size: "5" } });
            const listStub = sinon.stub(service, "listAssets").resolves([]);

            await controller.listAssets(req as any, res as any);

            expect(listStub).to.have.been.calledWith(
                projectId,
                sinon.match({ lastFetchedIndexId: 42, size: 5 }),
            );
            expect(res.status).to.have.been.calledWith(200);
            expect(res.json).to.have.been.calledWith({
                records: [],
                recordCount: 0,
                lastFetchedIndexId: -1,
            });
        });

        it("should return 500 on unexpected error", async () => {
            req = mockRequest({ params: { projectId }, query: {} });
            sinon.stub(service, "listAssets").throws(new Error("DB error"));

            await controller.listAssets(req as any, res as any);

            expect(res.status).to.have.been.calledWith(500);
        });
    });

    describe("getAsset", () => {
        it("should return 200 with the asset", async () => {
            req = mockRequest({ params: { projectId, assetId } });
            sinon.stub(service, "getAssetById").resolves(sampleAsset);

            await controller.getAsset(req as any, res as any);

            expect(res.status).to.have.been.calledWith(200);
            expect(res.json).to.have.been.calledWith(sampleAsset);
        });

        it("should return 404 when asset not found", async () => {
            req = mockRequest({ params: { projectId, assetId } });
            sinon.stub(service, "getAssetById").throws(new NotFoundError("Not found"));

            await controller.getAsset(req as any, res as any);

            expect(res.status).to.have.been.calledWith(404);
        });
    });

    describe("createAsset", () => {
        it("should return 201 with the created asset", async () => {
            req = mockRequest({ params: { projectId }, body: { name: "Chiller Unit 1", assetTypeId } });
            sinon.stub(service, "createAsset").resolves(sampleAsset);

            await controller.createAsset(req as any, res as any);

            expect(res.status).to.have.been.calledWith(201);
            expect(res.json).to.have.been.calledWith(sampleAsset);
        });

        it("should return 404 when asset type not found", async () => {
            req = mockRequest({ params: { projectId }, body: { name: "Chiller Unit 1", assetTypeId } });
            sinon.stub(service, "createAsset").throws(new NotFoundError("AssetType with id not found"));

            await controller.createAsset(req as any, res as any);

            expect(res.status).to.have.been.calledWith(404);
        });

        it("should return 500 on unexpected error", async () => {
            req = mockRequest({ params: { projectId }, body: { name: "Chiller Unit 1", assetTypeId } });
            sinon.stub(service, "createAsset").throws(new Error("DB error"));

            await controller.createAsset(req as any, res as any);

            expect(res.status).to.have.been.calledWith(500);
        });
    });

    describe("patchAsset", () => {
        it("should return 200 with the updated asset", async () => {
            const updatedAsset: Asset = { ...sampleAsset, name: "Chiller Unit 2", lastModifiedBy: "user@example.com", lastModifiedOn: new Date("2026-07-10T00:00:00Z") };
            req = mockRequest({ params: { projectId, assetId }, body: { name: "Chiller Unit 2" } });
            const updateStub = sinon.stub(service, "updateAsset").resolves(updatedAsset);

            await controller.patchAsset(req as any, res as any);

            expect(updateStub).to.have.been.calledWith(projectId, assetId, sinon.match({ name: "Chiller Unit 2" }));
            expect(res.status).to.have.been.calledWith(200);
            expect(res.json).to.have.been.calledWith(updatedAsset);
        });

        it("should forward only the name to the service (relational fields are out of scope)", async () => {
            const parentAssetId = "22222222-3333-4444-8555-666666666666";
            const updatedAsset: Asset = { ...sampleAsset, name: "Chiller Unit 2", lastModifiedBy: "user@example.com", lastModifiedOn: new Date("2026-07-10T00:00:00Z") };
            req = mockRequest({ params: { projectId, assetId }, body: { name: "Chiller Unit 2", assetTypeId, parentAssetId } });
            const updateStub = sinon.stub(service, "updateAsset").resolves(updatedAsset);

            await controller.patchAsset(req as any, res as any);

            const patchArg = updateStub.firstCall.args[2];
            expect(patchArg).to.deep.equal({ name: "Chiller Unit 2" });
            expect(patchArg).to.not.have.property("assetTypeId");
            expect(patchArg).to.not.have.property("parentAssetId");
            expect(res.status).to.have.been.calledWith(200);
            expect(res.json).to.have.been.calledWith(updatedAsset);
        });

        it("should return 404 when asset not found", async () => {
            req = mockRequest({ params: { projectId, assetId }, body: { name: "Chiller Unit 2" } });
            sinon.stub(service, "updateAsset").throws(new NotFoundError("Not found"));

            await controller.patchAsset(req as any, res as any);

            expect(res.status).to.have.been.calledWith(404);
        });

        it("should return 500 on unexpected error", async () => {
            req = mockRequest({ params: { projectId, assetId }, body: { name: "Chiller Unit 2" } });
            sinon.stub(service, "updateAsset").throws(new Error("DB error"));

            await controller.patchAsset(req as any, res as any);

            expect(res.status).to.have.been.calledWith(500);
        });
    });
});