"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
const node_assert_1 = require("node:assert");
const messageTypes_js_1 = require("../../messageTypes.js");
const index_js_1 = require("../../opLifecycle/index.js");
// Make a mock op with distinguishable contents
function op(data = "Some Data") {
    // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
    return {
        type: messageTypes_js_1.ContainerMessageType.FluidDataStoreOp,
        contents: data,
    };
}
function nonDirtyableOp() {
    return {
        type: messageTypes_js_1.ContainerMessageType.GC,
        contents: {},
    };
}
const generateStringOfSize = (sizeInBytes) => "0".repeat(sizeInBytes);
const smallMessage = (size = 100) => {
    // JSON envelope of op returned by op fn above
    const contentSize = (size ?? 0) - JSON.stringify(op("")).length; // (36 chars overhead per op)
    return {
        runtimeOp: op(generateStringOfSize(contentSize)),
        referenceSequenceNumber: 0,
    };
};
describe("BatchManager", () => {
    const defaultOptions = { disableGroupedBatching: false };
    describe("addBatchMetadata", () => {
        const makeLocalBatch = (...ops) => ({
            messages: ops.map((data, i) => ({ runtimeOp: op(data), referenceSequenceNumber: i })),
            referenceSequenceNumber: ops.length - 1,
        });
        for (const includeBatchId of [true, false])
            it(`sets metadata correctly [${includeBatchId ? "with" : "without"} batchId]`, () => {
                const batchId = includeBatchId ? "BATCH_ID" : undefined;
                // Multi-message batch: first gets batch:true, last gets batch:false, middle untouched
                const batch = (0, index_js_1.addBatchMetadata)(makeLocalBatch("a", "b", "c"), batchId);
                node_assert_1.strict.deepEqual(batch.messages.map((m) => m.metadata), [
                    { batch: true, ...(includeBatchId ? { batchId } : undefined) }, // batchId should be omitted (not set to undefined) if not provided
                    undefined, // metadata not touched for intermediate messages
                    { batch: false },
                ]);
                // Single-message batch: no batch flag, only batchId if provided
                const singleOpBatch = (0, index_js_1.addBatchMetadata)(makeLocalBatch("a"), batchId);
                node_assert_1.strict.deepEqual(singleOpBatch.messages.map((m) => m.metadata), [
                    includeBatchId ? { batchId } : undefined, // batchId should be omitted (not set to undefined) if not provided
                ]);
            });
    });
    it("BatchId Format", () => {
        const clientId = "3627a2a9-963f-4e3b-a4d2-a31b1267ef29";
        const batchStartCsn = 123;
        const batchId = (0, index_js_1.generateBatchId)(clientId, batchStartCsn);
        const serialized = JSON.stringify({ batchId });
        node_assert_1.strict.equal(serialized, `{"batchId":"3627a2a9-963f-4e3b-a4d2-a31b1267ef29_[123]"}`);
    });
    it("Batch reference sequence number maps to the last message", () => {
        const batchManager = new index_js_1.BatchManager(defaultOptions);
        batchManager.push({ runtimeOp: op(), referenceSequenceNumber: 0 }, /* reentrant */ false);
        batchManager.push({ runtimeOp: op(), referenceSequenceNumber: 1 }, /* reentrant */ false);
        batchManager.push({ runtimeOp: op(), referenceSequenceNumber: 2 }, /* reentrant */ false);
        node_assert_1.strict.equal(batchManager.sequenceNumbers.referenceSequenceNumber, 2);
    });
    it("estimateSocketSize", () => {
        // 40 bytes of content + 200 bytes overhead
        node_assert_1.strict.equal((0, index_js_1.estimateSocketSize)((0, index_js_1.localBatchToOutboundBatch)({
            messages: [smallMessage(40)],
            referenceSequenceNumber: 0,
        })), 240);
        const messages = [];
        for (let i = 0; i < 10; i++) {
            messages.push(smallMessage(40));
        }
        // (40 bytes of content + 200 bytes overhead) x 10
        node_assert_1.strict.equal((0, index_js_1.estimateSocketSize)((0, index_js_1.localBatchToOutboundBatch)({ messages, referenceSequenceNumber: 0 })), 2400);
    });
    it("disableGroupedBatching is accessible via options", () => {
        node_assert_1.strict.equal(new index_js_1.BatchManager({ disableGroupedBatching: true }).options.disableGroupedBatching, true);
        node_assert_1.strict.equal(new index_js_1.BatchManager({ disableGroupedBatching: false }).options.disableGroupedBatching, false);
    });
    it("Batch op reentry state preserved during its lifetime", () => {
        const batchManager = new index_js_1.BatchManager(defaultOptions);
        batchManager.push({ runtimeOp: op(), referenceSequenceNumber: 0 }, /* reentrant */ false);
        batchManager.push({ runtimeOp: op(), referenceSequenceNumber: 1 }, /* reentrant */ false);
        batchManager.push({ runtimeOp: op(), referenceSequenceNumber: 2 }, /* reentrant */ false);
        node_assert_1.strict.equal(batchManager.popBatch().hasReentrantOps, false);
        batchManager.push({ runtimeOp: op(), referenceSequenceNumber: 0 }, /* reentrant */ false);
        batchManager.push({ runtimeOp: op(), referenceSequenceNumber: 1 }, 
        /* reentrant */ true, 
        /* currentClientSequenceNumber */ undefined);
        batchManager.push({ runtimeOp: op(), referenceSequenceNumber: 2 }, /* reentrant */ false);
        node_assert_1.strict.equal(batchManager.popBatch().hasReentrantOps, true);
        batchManager.push({ runtimeOp: op(), referenceSequenceNumber: 0 }, /* reentrant */ false);
        node_assert_1.strict.equal(batchManager.popBatch().hasReentrantOps, false);
    });
    it("should rollback to checkpoint correctly", () => {
        const batchManager = new index_js_1.BatchManager(defaultOptions);
        // Push initial messages
        batchManager.push(smallMessage(), /* reentrant */ false);
        batchManager.push(smallMessage(), /* reentrant */ false);
        // Create checkpoint
        const checkpoint = batchManager.checkpoint();
        // Push more messages
        batchManager.push(smallMessage(), /* reentrant */ false);
        batchManager.push(smallMessage(), /* reentrant */ false);
        // Rollback to checkpoint
        checkpoint.rollback((message) => {
            // Process rollback message (no-op in this test)
        });
        // Verify state after rollback
        node_assert_1.strict.equal(batchManager.length, 2);
    });
    it("should handle rollback with no additional messages", () => {
        const batchManager = new index_js_1.BatchManager(defaultOptions);
        // Push initial messages
        batchManager.push(smallMessage(), /* reentrant */ false);
        // Create checkpoint
        const checkpoint = batchManager.checkpoint();
        // Rollback to checkpoint without pushing more messages
        checkpoint.rollback((message) => {
            // Process rollback message (no-op in this test)
        });
        // Verify state after rollback
        node_assert_1.strict.equal(batchManager.length, 1);
    });
    it("should throw error if ops are generated during rollback", () => {
        const batchManager = new index_js_1.BatchManager(defaultOptions);
        // Push initial messages
        batchManager.push(smallMessage(), /* reentrant */ false);
        // Create checkpoint
        const checkpoint = batchManager.checkpoint();
        // Push more messages
        batchManager.push(smallMessage(), /* reentrant */ false);
        // Attempt rollback and generate ops during rollback
        node_assert_1.strict.throws(() => {
            checkpoint.rollback((message) => {
                // Generate ops during rollback
                batchManager.push(smallMessage(), /* reentrant */ false);
            });
        }, /Error: Ops generated during rollback/);
    });
    it("Popping the batch then rolling is not allowed", () => {
        const batchManager = new index_js_1.BatchManager(defaultOptions);
        batchManager.push({ runtimeOp: op(), referenceSequenceNumber: 0 }, /* reentrant */ false);
        const checkpoint = batchManager.checkpoint();
        batchManager.popBatch();
        // Attempt rollback and generate ops during rollback
        node_assert_1.strict.throws(() => {
            checkpoint.rollback((message) => {
                // Generate ops during rollback
                batchManager.push(smallMessage(), /* reentrant */ false);
            });
        }, /Error: Ops generated during rollback/);
    });
    describe("containsUserChanges", () => {
        // default op used in other tests is dirtyable
        const dirtyableOp = op;
        it("returns false when there are no messages", () => {
            const batchManager = new index_js_1.BatchManager(defaultOptions);
            node_assert_1.strict.equal(batchManager.containsUserChanges(), false, "Should be false for empty batch");
        });
        it("returns true if at least one message is dirtyable", () => {
            const batchManager = new index_js_1.BatchManager(defaultOptions);
            batchManager.push({ runtimeOp: dirtyableOp(), referenceSequenceNumber: 0 }, false);
            node_assert_1.strict.equal(batchManager.containsUserChanges(), true, "Should be true if dirtyable op present");
        });
        it("returns false if all messages are non-dirtyable", () => {
            const batchManager = new index_js_1.BatchManager(defaultOptions);
            batchManager.push({ runtimeOp: nonDirtyableOp(), referenceSequenceNumber: 0 }, false);
            batchManager.push({ runtimeOp: nonDirtyableOp(), referenceSequenceNumber: 1 }, false);
            node_assert_1.strict.equal(batchManager.containsUserChanges(), false, "Should be false if no dirtyable ops");
        });
        it("returns true if mixed dirtyable and non-dirtyable messages", () => {
            const batchManager = new index_js_1.BatchManager(defaultOptions);
            batchManager.push({ runtimeOp: nonDirtyableOp(), referenceSequenceNumber: 0 }, false);
            batchManager.push({ runtimeOp: dirtyableOp(), referenceSequenceNumber: 1 }, false);
            batchManager.push({ runtimeOp: nonDirtyableOp(), referenceSequenceNumber: 2 }, false);
            node_assert_1.strict.equal(batchManager.containsUserChanges(), true, "Should be true if any dirtyable op present");
        });
    });
});
//# sourceMappingURL=batchManager.spec.js.map