"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 container_definitions_1 = require("@fluidframework/container-definitions");
const internal_1 = require("@fluidframework/runtime-definitions/internal");
const internal_2 = require("@fluidframework/telemetry-utils/internal");
const channelCollection_js_1 = require("../channelCollection.js");
const messageTypes_js_1 = require("../messageTypes.js");
const index_js_1 = require("../summary/index.js");
const dataStoreCreationHelper_js_1 = require("./dataStoreCreationHelper.js");
describe("Runtime", () => {
    describe("ChannelCollection", () => {
        describe("getSummaryForDatastores", () => {
            const enabledMetadata = {
                summaryFormatVersion: 1,
                message: undefined,
            };
            const disabledMetadata = {
                summaryFormatVersion: 1,
                disableIsolatedChannels: true,
                message: undefined,
            };
            const emptyTree = (id) => ({
                id,
                blobs: {},
                trees: {},
            });
            const testSnapshot = {
                id: "root-id",
                blobs: {},
                trees: {
                    [internal_1.channelsTreeName]: {
                        id: "channels-id",
                        blobs: {},
                        trees: {
                            [index_js_1.nonDataStorePaths[0]]: emptyTree("lower-non-datastore-1"),
                            "some-datastore": emptyTree("lower-datastore-1"),
                            [index_js_1.nonDataStorePaths[1]]: emptyTree("lower-non-datastore-2"),
                            "another-datastore": emptyTree("lower-datastore-2"),
                        },
                    },
                    [index_js_1.nonDataStorePaths[0]]: emptyTree("top-non-datastore-1"),
                    "some-datastore": emptyTree("top-datastore-1"),
                    [index_js_1.nonDataStorePaths[1]]: emptyTree("top-non-datastore-2"),
                    "another-datastore": emptyTree("top-datastore-2"),
                },
            };
            it("Should return undefined for undefined snapshots", () => {
                let snapshot = (0, channelCollection_js_1.getSummaryForDatastores)(undefined, undefined);
                (0, node_assert_1.strict)(snapshot === undefined);
                snapshot = (0, channelCollection_js_1.getSummaryForDatastores)(undefined, enabledMetadata);
                (0, node_assert_1.strict)(snapshot === undefined);
                snapshot = (0, channelCollection_js_1.getSummaryForDatastores)(undefined, disabledMetadata);
                (0, node_assert_1.strict)(snapshot === undefined);
                snapshot = (0, channelCollection_js_1.getSummaryForDatastores)(undefined, undefined);
                (0, node_assert_1.strict)(snapshot === undefined);
                snapshot = (0, channelCollection_js_1.getSummaryForDatastores)(undefined, enabledMetadata);
                (0, node_assert_1.strict)(snapshot === undefined);
                snapshot = (0, channelCollection_js_1.getSummaryForDatastores)(undefined, disabledMetadata);
                (0, node_assert_1.strict)(snapshot === undefined);
            });
            it("Should strip out non-datastore paths for versions < 1", () => {
                const snapshot = (0, channelCollection_js_1.getSummaryForDatastores)(testSnapshot, undefined);
                (0, node_assert_1.strict)(snapshot, "Snapshot should be defined");
                node_assert_1.strict.strictEqual(snapshot.id, "root-id", "Should be top-level");
                node_assert_1.strict.strictEqual(Object.keys(snapshot.trees).length, 3, "Should have 3 datastores");
                node_assert_1.strict.strictEqual(snapshot.trees[internal_1.channelsTreeName]?.id, "channels-id", "Should have channels tree as datastore");
                node_assert_1.strict.strictEqual(snapshot.trees["some-datastore"]?.id, "top-datastore-1", "Should have top datastore 1");
                node_assert_1.strict.strictEqual(snapshot.trees["another-datastore"]?.id, "top-datastore-2", "Should have top datastore 2");
            });
            it("Should strip out non-datastore paths for disabled isolated channels", () => {
                const snapshot = (0, channelCollection_js_1.getSummaryForDatastores)(testSnapshot, disabledMetadata);
                (0, node_assert_1.strict)(snapshot, "Snapshot should be defined");
                node_assert_1.strict.strictEqual(snapshot.id, "root-id", "Should be top-level");
                node_assert_1.strict.strictEqual(Object.keys(snapshot.trees).length, 3, "Should have 3 datastores");
                node_assert_1.strict.strictEqual(snapshot.trees[internal_1.channelsTreeName]?.id, "channels-id", "Should have channels tree as datastore");
                node_assert_1.strict.strictEqual(snapshot.trees["some-datastore"]?.id, "top-datastore-1", "Should have top datastore 1");
                node_assert_1.strict.strictEqual(snapshot.trees["another-datastore"]?.id, "top-datastore-2", "Should have top datastore 2");
            });
            it("Should give channels subtree for version 1", () => {
                const snapshot = (0, channelCollection_js_1.getSummaryForDatastores)(testSnapshot, enabledMetadata);
                (0, node_assert_1.strict)(snapshot, "Snapshot should be defined");
                node_assert_1.strict.strictEqual(snapshot.id, "channels-id", "Should be lower-level");
                node_assert_1.strict.strictEqual(Object.keys(snapshot.trees).length, 4, "Should have 4 datastores");
                // Put in variable to avoid type-narrowing bug
                const nonDataStore1 = snapshot.trees[index_js_1.nonDataStorePaths[0]];
                node_assert_1.strict.strictEqual(nonDataStore1?.id, "lower-non-datastore-1", "Should have lower non-datastore 1");
                node_assert_1.strict.strictEqual(snapshot.trees[index_js_1.nonDataStorePaths[1]]?.id, "lower-non-datastore-2", "Should have lower non-datastore 2");
                node_assert_1.strict.strictEqual(snapshot.trees["some-datastore"]?.id, "lower-datastore-1", "Should have lower datastore 1");
                node_assert_1.strict.strictEqual(snapshot.trees["another-datastore"]?.id, "lower-datastore-2", "Should have lower datastore 2");
            });
        });
        describe("detectOutboundReferences", () => {
            it("Can find handles", () => {
                const outboundReferences = [];
                (0, channelCollection_js_1.detectOutboundReferences)("dataStore1", {
                    address: "dds1",
                    someHandle: {
                        type: "__fluid_handle__",
                        url: "routeA",
                    },
                    nested: {
                        anotherHandle: {
                            type: "__fluid_handle__",
                            url: "routeB",
                        },
                        address: "ignored",
                    },
                    array: [
                        {
                            type: "__fluid_handle__",
                            url: "routeC",
                        },
                        {
                            type: "__fluid_handle__",
                            url: "routeD",
                        },
                    ],
                    // eslint-disable-next-line unicorn/no-null
                    deadEnd: null,
                    number: 1,
                    nothing: undefined,
                }, (from, to) => {
                    outboundReferences.push([from, to]);
                });
                node_assert_1.strict.deepEqual(outboundReferences, [
                    ["/dataStore1/dds1", "routeA"],
                    ["/dataStore1/dds1", "routeB"],
                    ["/dataStore1/dds1", "routeC"],
                    ["/dataStore1/dds1", "routeD"],
                ], "Should find both handles");
            });
            it("null contents", () => {
                // eslint-disable-next-line unicorn/no-null
                (0, channelCollection_js_1.detectOutboundReferences)("foo", null, () => {
                    node_assert_1.strict.fail("Should not be called");
                });
            });
        });
        describe("processAttachMessages - Duplicate ID Detection", () => {
            /* eslint-disable @typescript-eslint/consistent-type-assertions */
            let channelCollection;
            let mockLogger;
            let parentContext;
            const createMockAttachMessage = (id, type = "TestDataStore") => {
                const message = {
                    id,
                    type,
                    snapshot: { id: "snapshot-id", entries: [], blobs: [] },
                };
                return message;
            };
            const createMockMessageCollection = (attachMessage, local = false, sequenceNumber = 1) => {
                const messageCollection = {
                    envelope: {
                        type: messageTypes_js_1.ContainerMessageType.Attach,
                        contents: attachMessage,
                        sequenceNumber,
                        clientId: local ? "local-client" : "remote-client",
                        timestamp: Date.now(),
                    },
                    messagesContent: [
                        {
                            contents: attachMessage,
                            localOpMetadata: undefined,
                            clientSequenceNumber: 1,
                        },
                    ],
                    local,
                };
                return messageCollection;
            };
            const configProvider = (settings) => ({
                getRawConfig: (name) => settings[name],
            });
            beforeEach(() => {
                mockLogger = new internal_2.MockLogger();
                const mc = (0, internal_2.mixinMonitoringContext)(mockLogger, configProvider({
                    "Fluid.Runtime.DisableShortIds": true,
                }));
                const baseParentContext = (0, dataStoreCreationHelper_js_1.createParentContext)(mc.logger);
                // Create a proper IFluidRootParentContextPrivate with root-level submitMessage/submitSignal
                parentContext = {
                    ...baseParentContext,
                    attachState: container_definitions_1.AttachState.Attached,
                    submitMessage: (_containerRuntimeMessage, _localOpMetadata) => { },
                    submitSignal: (_envelope, _targetClientId) => { },
                    addedGCOutboundRoute: () => { },
                    makeLocallyVisible: () => { },
                    getExtension: () => undefined,
                    getCreateChildSummarizerNodeFn: (id) => {
                        const fn = (0, dataStoreCreationHelper_js_1.createSummarizerNodeAndGetCreateFn)(id).createSummarizerNodeFn;
                        return fn;
                    },
                };
                channelCollection = new channelCollection_js_1.ChannelCollection(undefined, // baseSnapshot
                parentContext, mockLogger, () => { }, // gcNodeUpdated
                () => false, // isDataStoreDeleted
                new Map());
            });
            it("should throw DataProcessingError for duplicate ID with unbound (not-yet-attached) DataStore", () => {
                // Create a local DataStore context (it's unbound) and get its ID so we can simulate a remote attach with the same ID
                const localContext = channelCollection.createDataStoreContext(["TestPackage"]);
                const dataStoreId = localContext.id;
                // Try to process a remote attach message with the same ID
                const attachMessage = createMockAttachMessage(dataStoreId);
                const messageCollection = createMockMessageCollection(attachMessage, false);
                // Should throw DataProcessingError
                node_assert_1.strict.throws(() => {
                    channelCollection.processMessages(messageCollection);
                }, (error) => {
                    (0, node_assert_1.strict)(error instanceof internal_2.DataProcessingError, "Expected DataProcessingError");
                    (0, node_assert_1.strict)(error.message.includes("Local DataStore matches remote DataStore id"), `Expected error message about local DataStore match, got: ${error.message}`);
                    return true;
                }, "Should throw DataProcessingError for unbound context collision");
            });
            for (const state of [container_definitions_1.AttachState.Attaching, container_definitions_1.AttachState.Attached]) {
                it(`should throw DataCorruptionError for duplicate ID with ${state} DataStore`, () => {
                    // Create a local DataStore context and make it bound/attached
                    const localContext = channelCollection.createDataStoreContext([
                        "TestPackage",
                    ]);
                    const dataStoreId = localContext.id;
                    // Simulate localContext.makeLocallyVisible()
                    localContext.setAttachState(container_definitions_1.AttachState.Attaching);
                    channelCollection.contexts.bind(dataStoreId);
                    // If we're testing Attaching state, we're already there.
                    // If testing Attached, simulate receiving the attach op and transitioning to that state.
                    if (state === container_definitions_1.AttachState.Attached) {
                        localContext.setAttachState(container_definitions_1.AttachState.Attached);
                    }
                    // Try to process a remote attach message with the same ID
                    const attachMessage = createMockAttachMessage(dataStoreId);
                    const messageCollection = createMockMessageCollection(attachMessage, false);
                    // Should throw DataCorruptionError
                    node_assert_1.strict.throws(() => {
                        channelCollection.processMessages(messageCollection);
                    }, (error) => {
                        (0, node_assert_1.strict)(error instanceof internal_2.DataCorruptionError, "Expected DataCorruptionError");
                        (0, node_assert_1.strict)(error.message.includes("Duplicate DataStore created with existing id"), `Expected error message about duplicate DataStore, got: ${error.message}`);
                        return true;
                    }, "Should throw DataCorruptionError for bound context collision");
                });
            }
            it("should throw DataCorruptionError for duplicate ID with aliased DataStore", () => {
                const alias = "my-alias";
                // Create a datastore and alias it
                const localContext = channelCollection.createDataStoreContext([
                    "TestPackage",
                ]);
                const dataStoreId = localContext.id;
                localContext.setAttachState(container_definitions_1.AttachState.Attaching);
                localContext.setAttachState(container_definitions_1.AttachState.Attached);
                channelCollection.contexts.bind(dataStoreId);
                // Simulate aliasing (which is usually mediated by ops)
                channelCollection.aliasMap.set(alias, dataStoreId);
                // Simulate an incoming attach message with the same alias
                const attachMessage = createMockAttachMessage(alias);
                const messageCollection = createMockMessageCollection(attachMessage, false);
                // Should throw DataCorruptionError
                node_assert_1.strict.throws(() => {
                    channelCollection.processMessages(messageCollection);
                }, (error) => {
                    (0, node_assert_1.strict)(error instanceof internal_2.DataCorruptionError, "Expected DataCorruptionError");
                    (0, node_assert_1.strict)(error.message.includes("Duplicate DataStore created with existing id"), `Expected error message about duplicate DataStore, got: ${error.message}`);
                    return true;
                }, "Should throw DataCorruptionError for aliased context collision");
            });
            /* eslint-enable @typescript-eslint/consistent-type-assertions */
        });
    });
});
//# sourceMappingURL=channelCollection.spec.js.map