"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 client_utils_1 = require("@fluid-internal/client-utils");
const container_definitions_1 = require("@fluidframework/container-definitions");
const internal_1 = require("@fluidframework/container-definitions/internal");
const internal_2 = require("@fluidframework/core-utils/internal");
const internal_3 = require("@fluidframework/datastore/internal");
const driver_definitions_1 = require("@fluidframework/driver-definitions");
const internal_4 = require("@fluidframework/runtime-definitions/internal");
const internal_5 = require("@fluidframework/runtime-utils/internal");
const internal_6 = require("@fluidframework/telemetry-utils/internal");
const internal_7 = require("@fluidframework/test-runtime-utils/internal");
const channelCollection_js_1 = require("../channelCollection.js");
const dataStore_js_1 = require("../dataStore.js");
const dataStoreContext_js_1 = require("../dataStoreContext.js");
const storageServiceWithAttachBlobs_js_1 = require("../storageServiceWithAttachBlobs.js");
const index_js_1 = require("../summary/index.js");
const dataStoreCreationHelper_js_1 = require("./dataStoreCreationHelper.js");
describe("Data Store Context Tests", () => {
    const dataStoreId = "Test1";
    const emptyGCData = { gcNodes: {} };
    let createSummarizerNodeFn;
    describe("LocalFluidDataStoreContext", () => {
        let localDataStoreContext;
        const storage = {};
        const scope = {};
        const makeLocallyVisibleFn = () => { };
        let parentContext;
        let summarizerNode;
        beforeEach(async () => {
            ({ summarizerNode, createSummarizerNodeFn } =
                (0, dataStoreCreationHelper_js_1.createSummarizerNodeAndGetCreateFn)(dataStoreId));
            parentContext = (0, dataStoreCreationHelper_js_1.createParentContext)();
        });
        describe("Initialization", () => {
            it("rejects ids with forward slashes", async () => {
                const invalidId = "beforeSlash/afterSlash";
                const codeBlock = () => new dataStoreContext_js_1.LocalFluidDataStoreContext({
                    id: invalidId,
                    pkg: ["TestDataStore1"],
                    parentContext,
                    storage,
                    scope,
                    createSummarizerNodeFn,
                    makeLocallyVisibleFn,
                    snapshotTree: undefined,
                });
                node_assert_1.strict.throws(codeBlock, (0, internal_7.validateAssertionError)("Data store ID contains slash"));
            });
            it("Errors thrown during realize are wrapped as DataProcessingError", async () => {
                const fullPackageName = ["BOGUS1", "BOGUS2"];
                localDataStoreContext = new dataStoreContext_js_1.LocalFluidDataStoreContext({
                    id: dataStoreId,
                    pkg: fullPackageName, // This will cause an error when calling `realizeCore`
                    parentContext,
                    storage,
                    scope,
                    createSummarizerNodeFn,
                    makeLocallyVisibleFn,
                    snapshotTree: undefined,
                });
                try {
                    await localDataStoreContext.realize();
                    node_assert_1.strict.fail("realize should have thrown an error due to empty pkg array");
                }
                catch (error) {
                    (0, node_assert_1.strict)((0, internal_6.isFluidError)(error), "Expected a valid Fluid Error to be thrown");
                    node_assert_1.strict.equal(error.errorType, internal_1.ContainerErrorTypes.dataProcessingError, "Error should be a DataProcessingError");
                    const props = error.getTelemetryProperties();
                    node_assert_1.strict.strictEqual(props.fullPackageName?.value, fullPackageName.join("/"), "The error should have the full package name in its telemetry properties");
                    node_assert_1.strict.equal(props.failedPkgPath?.value, "BOGUS1", "The error should have the failed package path in its telemetry properties");
                    node_assert_1.strict.equal(props.fluidDataStoreId?.value, "Test1", "The error should have the fluidDataStoreId in its telemetry properties");
                }
            });
            it("can initialize correctly and generate attributes", async () => {
                localDataStoreContext = new dataStoreContext_js_1.LocalFluidDataStoreContext({
                    id: dataStoreId,
                    pkg: ["TestDataStore1"],
                    parentContext,
                    storage,
                    scope,
                    createSummarizerNodeFn,
                    makeLocallyVisibleFn,
                    snapshotTree: undefined,
                });
                await localDataStoreContext.realize();
                const attachSummary = localDataStoreContext.getAttachSummary();
                const snapshot = (0, internal_5.convertSummaryTreeToITree)(attachSummary.summary);
                const attributesEntry = snapshot.entries.find((e) => e.path === index_js_1.dataStoreAttributesBlobName);
                (0, node_assert_1.strict)(attributesEntry !== undefined, "There is no attributes blob in the summary tree");
                // Assume that it is in write format, will see errors if not.
                const contents = JSON.parse(attributesEntry.value.contents);
                const dataStoreAttributes = {
                    pkg: JSON.stringify(["TestDataStore1"]),
                    summaryFormatVersion: 2,
                    isRootDataStore: false,
                };
                node_assert_1.strict.strictEqual(contents.pkg, dataStoreAttributes.pkg, "Local DataStore package does not match.");
                node_assert_1.strict.strictEqual(contents.summaryFormatVersion, dataStoreAttributes.summaryFormatVersion, "Local DataStore snapshot version does not match.");
                node_assert_1.strict.strictEqual(contents.isRootDataStore, dataStoreAttributes.isRootDataStore, "Local DataStore root state does not match");
                node_assert_1.strict.strictEqual((0, channelCollection_js_1.getLocalDataStoreType)(localDataStoreContext), "TestDataStore1", "Attach message type does not match.");
            });
            it("should generate exception when incorrectly created with array of packages", async () => {
                let exception = false;
                localDataStoreContext = new dataStoreContext_js_1.LocalFluidDataStoreContext({
                    id: dataStoreId,
                    pkg: ["TestComp", "SubComp"],
                    parentContext,
                    storage,
                    scope,
                    createSummarizerNodeFn,
                    makeLocallyVisibleFn,
                    snapshotTree: undefined,
                });
                await localDataStoreContext.realize().catch((error) => {
                    exception = true;
                });
                node_assert_1.strict.strictEqual(exception, true, "Exception did not occur.");
            });
            it("can initialize and generate attributes when correctly created with array of packages", async () => {
                const registryWithSubRegistries = {
                    get IFluidDataStoreFactory() {
                        return registryWithSubRegistries;
                    },
                    get IFluidDataStoreRegistry() {
                        return registryWithSubRegistries;
                    },
                    get: async (pkg) => registryWithSubRegistries,
                    type: "store-type",
                    instantiateDataStore: async (context) => new internal_7.MockFluidDataStoreRuntime(),
                };
                parentContext = {
                    IFluidDataStoreRegistry: registryWithSubRegistries,
                    clientDetails: {},
                    deltaManager: new internal_7.MockDeltaManager(),
                    isReadOnly: () => false,
                };
                localDataStoreContext = new dataStoreContext_js_1.LocalFluidDataStoreContext({
                    id: dataStoreId,
                    pkg: ["TestComp", "SubComp"],
                    parentContext,
                    storage,
                    scope,
                    createSummarizerNodeFn,
                    makeLocallyVisibleFn,
                    snapshotTree: undefined,
                });
                await localDataStoreContext.realize();
                const attachSummary = localDataStoreContext.getAttachSummary();
                const snapshot = (0, internal_5.convertSummaryTreeToITree)(attachSummary.summary);
                const attributesEntry = snapshot.entries.find((e) => e.path === index_js_1.dataStoreAttributesBlobName);
                (0, node_assert_1.strict)(attributesEntry !== undefined, "There is no attributes blob in the summary tree");
                const contents = JSON.parse(attributesEntry.value.contents);
                const dataStoreAttributes = {
                    pkg: JSON.stringify(["TestComp", "SubComp"]),
                    summaryFormatVersion: 2,
                    isRootDataStore: false,
                };
                node_assert_1.strict.strictEqual(contents.pkg, dataStoreAttributes.pkg, "Local DataStore package does not match.");
                node_assert_1.strict.strictEqual(contents.summaryFormatVersion, dataStoreAttributes.summaryFormatVersion, "Local DataStore snapshot version does not match.");
                node_assert_1.strict.strictEqual(contents.isRootDataStore, dataStoreAttributes.isRootDataStore, "Local DataStore root state does not match");
                node_assert_1.strict.strictEqual((0, channelCollection_js_1.getLocalDataStoreType)(localDataStoreContext), "SubComp", "Attach message type does not match.");
            });
            it("can correctly initialize non-root context", async () => {
                localDataStoreContext = new dataStoreContext_js_1.LocalFluidDataStoreContext({
                    id: dataStoreId,
                    pkg: ["TestDataStore1"],
                    parentContext,
                    storage,
                    scope,
                    createSummarizerNodeFn,
                    makeLocallyVisibleFn,
                    snapshotTree: undefined,
                });
                const isRootNode = await localDataStoreContext.isRoot();
                node_assert_1.strict.strictEqual(isRootNode, false, "The data store should not be root.");
            });
        });
        describe("Local data stores in summarizer client", () => {
            let mockLogger;
            const packageName = ["TestDataStore1"];
            beforeEach(async () => {
                // Change the container runtime's logger to MockLogger and its type to be a summarizer client.
                mockLogger = new internal_6.MockLogger();
                const clientDetails = {
                    capabilities: {
                        interactive: false,
                    },
                    type: index_js_1.summarizerClientType,
                };
                parentContext = (0, dataStoreCreationHelper_js_1.createParentContext)(mockLogger, clientDetails);
            });
            it("logs when local data store is created in summarizer", async () => {
                localDataStoreContext = new dataStoreContext_js_1.LocalFluidDataStoreContext({
                    id: dataStoreId,
                    pkg: packageName,
                    parentContext,
                    storage,
                    scope,
                    createSummarizerNodeFn,
                    makeLocallyVisibleFn,
                    snapshotTree: undefined,
                });
                const expectedEvents = [
                    {
                        eventName: "FluidDataStoreContext:DataStoreCreatedWhileReadonly",
                        fullPackageName: {
                            tag: internal_6.TelemetryDataTag.CodeArtifact,
                            value: packageName.join("/"),
                        },
                        fluidDataStoreId: {
                            tag: internal_6.TelemetryDataTag.CodeArtifact,
                            value: dataStoreId,
                        },
                    },
                ];
                mockLogger.assertMatch(expectedEvents, "data store create event not generated as expected");
            });
            it("logs when local data store sends op in summarizer", async () => {
                localDataStoreContext = new dataStoreContext_js_1.LocalFluidDataStoreContext({
                    id: dataStoreId,
                    pkg: packageName,
                    parentContext,
                    storage,
                    scope,
                    createSummarizerNodeFn,
                    makeLocallyVisibleFn,
                    snapshotTree: undefined,
                });
                await localDataStoreContext.realize();
                const message = {
                    type: internal_3.DataStoreMessageType.ChannelOp,
                    content: {
                        address: "address",
                        contents: "summarizer message",
                    },
                };
                localDataStoreContext.submitMessage(message.type, message.content, {});
                const expectedEvents = [
                    {
                        eventName: "FluidDataStoreContext:DataStoreMessageWhileReadonly",
                        type: internal_3.DataStoreMessageType.ChannelOp,
                        fluidDataStoreId: {
                            tag: internal_6.TelemetryDataTag.CodeArtifact,
                            value: dataStoreId,
                        },
                        fullPackageName: {
                            tag: internal_6.TelemetryDataTag.CodeArtifact,
                            value: packageName.join("/"),
                        },
                    },
                ];
                mockLogger.assertMatch(expectedEvents, "data store message submitted event not generated as expected");
            });
            it("logs maximum of 10 local summarizer events per data store", async () => {
                localDataStoreContext = new dataStoreContext_js_1.LocalFluidDataStoreContext({
                    id: dataStoreId,
                    pkg: packageName,
                    parentContext,
                    storage,
                    scope,
                    createSummarizerNodeFn,
                    makeLocallyVisibleFn,
                    snapshotTree: undefined,
                });
                await localDataStoreContext.realize();
                let eventCount = 0;
                for (let i = 0; i < 15; i++) {
                    const message = {
                        type: internal_3.DataStoreMessageType.ChannelOp,
                        content: {
                            address: "address",
                            contents: `summarizer message ${i}`,
                        },
                    };
                    localDataStoreContext.submitMessage(message.type, message.content, {});
                }
                for (const event of mockLogger.events) {
                    if (event.eventName === "FluidDataStoreContext:DataStoreMessageWhileReadonly" ||
                        event.eventName === "FluidDataStoreContext:DataStoreCreatedWhileReadonly") {
                        eventCount++;
                    }
                }
                node_assert_1.strict.strictEqual(eventCount, 10, "There should be only 10 events per data store");
            });
        });
        describe("Garbage Collection", () => {
            it("can generate correct GC data", async () => {
                localDataStoreContext = new dataStoreContext_js_1.LocalFluidDataStoreContext({
                    id: dataStoreId,
                    pkg: ["TestDataStore1"],
                    parentContext,
                    storage,
                    scope,
                    createSummarizerNodeFn,
                    makeLocallyVisibleFn,
                    snapshotTree: undefined,
                });
                const gcData = await localDataStoreContext.getGCData();
                node_assert_1.strict.deepStrictEqual(gcData, emptyGCData, "GC data from getGCData should be empty.");
            });
            it("can successfully update referenced state", () => {
                localDataStoreContext = new dataStoreContext_js_1.LocalFluidDataStoreContext({
                    id: dataStoreId,
                    pkg: ["TestComp", "SubComp"],
                    parentContext,
                    storage,
                    scope,
                    createSummarizerNodeFn,
                    makeLocallyVisibleFn,
                    snapshotTree: undefined,
                });
                // Get the summarizer node for this data store which tracks its referenced state.
                const dataStoreSummarizerNode = summarizerNode.getChild(dataStoreId);
                node_assert_1.strict.strictEqual(dataStoreSummarizerNode?.isReferenced(), true, "Data store should be referenced by default");
                // Update the used routes to not include route to the data store.
                localDataStoreContext.updateUsedRoutes([]);
                node_assert_1.strict.strictEqual(dataStoreSummarizerNode?.isReferenced(), false, "Data store should now be unreferenced");
                // Add the data store's route (empty string) to its used routes.
                localDataStoreContext.updateUsedRoutes([""]);
                node_assert_1.strict.strictEqual(dataStoreSummarizerNode?.isReferenced(), true, "Data store should now be referenced");
            });
            it("can tombstone a local datastore", async () => {
                localDataStoreContext = new dataStoreContext_js_1.LocalFluidDataStoreContext({
                    id: dataStoreId,
                    pkg: ["TestComp", "SubComp"],
                    parentContext,
                    storage,
                    scope,
                    createSummarizerNodeFn,
                    makeLocallyVisibleFn,
                    snapshotTree: undefined,
                });
                localDataStoreContext.setTombstone(true);
                (0, node_assert_1.strict)(localDataStoreContext.tombstoned, `Local data store should be tombstoned!`);
                localDataStoreContext.setTombstone(false);
                (0, node_assert_1.strict)(!localDataStoreContext.tombstoned, `Local data store should not be tombstoned!`);
            });
        });
    });
    describe("RemoteDataStoreContext", () => {
        let remoteDataStoreContext;
        let dataStoreAttributes;
        const storage = {};
        const scope = {};
        let summarizerNode;
        let parentContext;
        beforeEach(async () => {
            const factory = {
                type: "store-type",
                get IFluidDataStoreFactory() {
                    return factory;
                },
                instantiateDataStore: async (context) => new internal_7.MockFluidDataStoreRuntime(),
            };
            const registry = {
                get IFluidDataStoreRegistry() {
                    return registry;
                },
                get: async (pkg) => factory,
            };
            parentContext = {
                IFluidDataStoreRegistry: registry,
                clientDetails: {},
                containerRuntime: parentContext,
                deltaManager: new internal_7.MockDeltaManager(),
                isReadOnly: () => false,
            };
        });
        describe("Initialization - can correctly initialize and generate attributes", () => {
            beforeEach(() => {
                summarizerNode = (0, index_js_1.createRootSummarizerNodeWithGC)((0, internal_6.createChildLogger)(), (() => undefined), 0, 0);
                summarizerNode.startSummary(0, (0, internal_6.createChildLogger)(), 0);
                createSummarizerNodeFn = (summarizeInternal, getGCDataFn) => summarizerNode.createChild(summarizeInternal, dataStoreId, { type: internal_4.CreateSummarizerNodeSource.FromSummary }, 
                // Disable GC for initialization tests.
                { gcDisabled: true }, getGCDataFn);
            });
            const pkgName = "TestDataStore1";
            /**
             * Runs the initialization and generate datastore attributes tests with the given write-mode preferences
             * and expectations.
             * This runs the same test with various summary write and read preferences. Specifically each call of this
             * function will run the test 4 times, one for each possible summary format we could be reading from.
             * @param expected - the expected datastore attributes to be generated given the write preference
             */
            function testGenerateAttributes(expected) {
                /**
                 * This function is called for each possible base snapshot format version. We want to cover all
                 * summary format read/write combinations. We only write in latest or -1 version, but we can
                 * need to be able to read old summary format versions forever.
                 * @param hasIsolatedChannels - whether we expect to read a snapshot tree with isolated channels or not
                 * @param attributes - datastore attributes that are in the base snapshot we load from
                 */
                async function testGenerateAttributesCore(attributes) {
                    const buffer = (0, client_utils_1.stringToBuffer)(JSON.stringify(attributes), "utf8");
                    const attachBlobs = new Map([
                        ["fluidDataStoreAttributes", buffer],
                    ]);
                    const snapshotTree = {
                        blobs: { [index_js_1.dataStoreAttributesBlobName]: "fluidDataStoreAttributes" },
                        trees: {},
                    };
                    // If we are expecting to read isolated channels as intended by the test, then make sure
                    // it exists on the snapshot. Otherwise, make sure it doesn't to most closely resemble
                    // real loading use cases.
                    snapshotTree.trees[internal_4.channelsTreeName] = {
                        blobs: {},
                        trees: {},
                    };
                    remoteDataStoreContext = new dataStoreContext_js_1.RemoteFluidDataStoreContext({
                        id: dataStoreId,
                        snapshot: snapshotTree,
                        parentContext,
                        storage: new storageServiceWithAttachBlobs_js_1.StorageServiceWithAttachBlobs(storage, attachBlobs),
                        scope,
                        createSummarizerNodeFn,
                    });
                    const isRootNode = await remoteDataStoreContext.isRoot();
                    node_assert_1.strict.strictEqual(isRootNode, true, "The data store should be root.");
                    const summarizeResult = await remoteDataStoreContext.summarize(true /* fullTree */);
                    (0, node_assert_1.strict)(summarizeResult.summary.type === driver_definitions_1.SummaryType.Tree, "summarize should always return a tree when fullTree is true");
                    const blob = summarizeResult.summary.tree[index_js_1.dataStoreAttributesBlobName];
                    const contents = JSON.parse(blob.content);
                    // Validate that generated attributes are as expected.
                    node_assert_1.strict.deepStrictEqual(contents, expected, "Unexpected datastore attributes written");
                }
                it("can read from latest with isolated channels", async () => testGenerateAttributesCore({
                    pkg: JSON.stringify([pkgName]),
                    summaryFormatVersion: 2,
                    isRootDataStore: true,
                }));
            }
            it("rejects ids with forward slashes", async () => {
                const invalidId = "beforeSlash/afterSlash";
                const codeBlock = () => new dataStoreContext_js_1.RemoteFluidDataStoreContext({
                    id: invalidId,
                    pkg: ["TestDataStore1"],
                    parentContext,
                    storage: storage,
                    scope,
                    createSummarizerNodeFn,
                    snapshot: undefined,
                });
                node_assert_1.strict.throws(codeBlock, (0, internal_7.validateAssertionError)("Data store ID contains slash"));
            });
            describe("writing with isolated channels enabled", () => testGenerateAttributes({
                pkg: JSON.stringify([pkgName]),
                summaryFormatVersion: 2,
                isRootDataStore: true,
            }));
        });
        describe("Garbage Collection", () => {
            // The base GC details of the root summarizer node. The child base GC details from this is passed on to the
            // child summarizer node during its creation.
            let rootBaseGCDetails;
            const getRootBaseGCDetails = async () => rootBaseGCDetails;
            /**
             * Given the GC data of a data store, build the GC data of the root (parent) node.
             */
            function buildRootGCData(dataStoreGCData, id) {
                const builder = new internal_5.GCDataBuilder();
                builder.prefixAndAddNodes(id, dataStoreGCData.gcNodes);
                return builder.getGCData();
            }
            beforeEach(() => {
                summarizerNode = (0, index_js_1.createRootSummarizerNodeWithGC)((0, internal_6.createChildLogger)(), (() => undefined), 0, 0, undefined, undefined, getRootBaseGCDetails);
                summarizerNode.startSummary(0, (0, internal_6.createChildLogger)(), 0);
                createSummarizerNodeFn = (summarizeInternal, getGCDataFn) => summarizerNode.createChild(summarizeInternal, dataStoreId, { type: internal_4.CreateSummarizerNodeSource.FromSummary }, undefined, getGCDataFn);
            });
            it("can generate GC data without base GC details in initial summary", async () => {
                dataStoreAttributes = {
                    pkg: "TestDataStore1",
                    summaryFormatVersion: undefined,
                };
                const buffer = (0, client_utils_1.stringToBuffer)(JSON.stringify(dataStoreAttributes), "utf8");
                const attachBlobs = new Map([
                    ["fluidDataStoreAttributes", buffer],
                ]);
                const snapshotTree = {
                    blobs: {
                        [index_js_1.dataStoreAttributesBlobName]: "fluidDataStoreAttributes",
                    },
                    trees: {},
                };
                remoteDataStoreContext = new dataStoreContext_js_1.RemoteFluidDataStoreContext({
                    id: dataStoreId,
                    snapshot: snapshotTree,
                    parentContext,
                    storage: new storageServiceWithAttachBlobs_js_1.StorageServiceWithAttachBlobs(storage, attachBlobs),
                    scope,
                    createSummarizerNodeFn,
                });
                const gcData = await remoteDataStoreContext.getGCData();
                node_assert_1.strict.deepStrictEqual(gcData, emptyGCData, "GC data from getGCData should be empty.");
            });
            it("can generate GC data with GC details in initial summary", async () => {
                dataStoreAttributes = {
                    pkg: "TestDataStore1",
                    summaryFormatVersion: undefined,
                };
                const attributesBuffer = (0, client_utils_1.stringToBuffer)(JSON.stringify(dataStoreAttributes), "utf8");
                const attachBlobs = new Map([
                    ["fluidDataStoreAttributes", attributesBuffer],
                ]);
                const snapshotTree = {
                    blobs: {
                        [index_js_1.dataStoreAttributesBlobName]: "fluidDataStoreAttributes",
                    },
                    trees: {},
                };
                // The base GC data of the data store.
                const dataStoreGCData = {
                    gcNodes: {
                        "/": ["/dds1", "/dds2"],
                        "/dds1": ["/dds2", "/"],
                    },
                };
                // Set the root base GC details to include the child node's base GC data.
                rootBaseGCDetails = {
                    usedRoutes: [],
                    gcData: buildRootGCData(dataStoreGCData, dataStoreId),
                };
                remoteDataStoreContext = new dataStoreContext_js_1.RemoteFluidDataStoreContext({
                    id: dataStoreId,
                    snapshot: snapshotTree,
                    parentContext,
                    storage: new storageServiceWithAttachBlobs_js_1.StorageServiceWithAttachBlobs(storage, attachBlobs),
                    scope,
                    createSummarizerNodeFn,
                });
                const gcData = await remoteDataStoreContext.getGCData();
                node_assert_1.strict.deepStrictEqual(gcData, dataStoreGCData, "GC data from getGCData is incorrect.");
            });
            it("should not reuse summary data when used state changed since last summary", async () => {
                dataStoreAttributes = {
                    pkg: "TestDataStore1",
                    summaryFormatVersion: undefined,
                };
                const attributesBuffer = (0, client_utils_1.stringToBuffer)(JSON.stringify(dataStoreAttributes), "utf8");
                const attachBlobs = new Map([
                    ["fluidDataStoreAttributes", attributesBuffer],
                ]);
                const snapshotTree = {
                    id: "dummy",
                    blobs: {
                        [index_js_1.dataStoreAttributesBlobName]: "fluidDataStoreAttributes",
                    },
                    trees: {},
                };
                // The base GC data of the data store.
                const dataStoreGCData = {
                    gcNodes: {
                        "/": [],
                    },
                };
                // Set the root base GC details to include the child node's base GC data.
                rootBaseGCDetails = {
                    usedRoutes: [`/${dataStoreId}`],
                    gcData: buildRootGCData(dataStoreGCData, dataStoreId),
                };
                remoteDataStoreContext = new dataStoreContext_js_1.RemoteFluidDataStoreContext({
                    id: dataStoreId,
                    snapshot: snapshotTree,
                    parentContext,
                    storage: new storageServiceWithAttachBlobs_js_1.StorageServiceWithAttachBlobs(storage, attachBlobs),
                    scope,
                    createSummarizerNodeFn,
                });
                // Since GC is enabled, GC must run before summarize. Get the GC data and update used routes to
                // emulate the GC process.
                const gcData = await remoteDataStoreContext.getGCData();
                node_assert_1.strict.deepStrictEqual(gcData, dataStoreGCData, "GC data from getGCData should be empty.");
                // Update used routes to the same as in initial GC details. This will ensure that the used state
                // matches the initial used state.
                remoteDataStoreContext.updateUsedRoutes([""]);
                // The data in the store has not changed since last summary and the reference used routes (from initial
                // used routes) and current used routes (default) are both empty. So, summarize should return a handle.
                let summarizeResult = await remoteDataStoreContext.summarize(false /* fullTree */);
                (0, node_assert_1.strict)(summarizeResult.summary.type === driver_definitions_1.SummaryType.Handle, "summarize should return a handle since nothing changed");
                // Update the used routes of the data store to a different value than current.
                remoteDataStoreContext.updateUsedRoutes([]);
                // Since the used state has changed, it should generate a full summary tree.
                summarizeResult = await remoteDataStoreContext.summarize(false /* fullTree */);
                (0, node_assert_1.strict)(summarizeResult.summary.type === driver_definitions_1.SummaryType.Tree, "summarize should return a tree since used state changed");
            });
            function updateReferencedStateTest() {
                const buffer = (0, client_utils_1.stringToBuffer)(JSON.stringify(dataStoreAttributes), "utf8");
                const attachBlobs = new Map([
                    ["fluidDataStoreAttributes", buffer],
                ]);
                const snapshotTree = {
                    id: "dummy",
                    blobs: { [".component"]: "fluidDataStoreAttributes" },
                    trees: {},
                };
                remoteDataStoreContext = new dataStoreContext_js_1.RemoteFluidDataStoreContext({
                    id: dataStoreId,
                    snapshot: snapshotTree,
                    parentContext,
                    storage: new storageServiceWithAttachBlobs_js_1.StorageServiceWithAttachBlobs(storage, attachBlobs),
                    scope,
                    createSummarizerNodeFn,
                });
                // Get the summarizer node for this data store which tracks its referenced state.
                const dataStoreSummarizerNode = summarizerNode.getChild(dataStoreId);
                node_assert_1.strict.strictEqual(dataStoreSummarizerNode?.isReferenced(), true, "Data store should be referenced by default");
                // Update the used routes to not include route to the data store.
                remoteDataStoreContext.updateUsedRoutes([]);
                node_assert_1.strict.strictEqual(dataStoreSummarizerNode?.isReferenced(), false, "Data store should now be unreferenced");
                // Add the data store's route (empty string) to its used routes.
                remoteDataStoreContext.updateUsedRoutes([""]);
                node_assert_1.strict.strictEqual(dataStoreSummarizerNode?.isReferenced(), true, "Data store should now be referenced");
            }
            it("can successfully update referenced state from format version 0", () => {
                dataStoreAttributes = {
                    pkg: "TestDataStore1",
                };
                updateReferencedStateTest();
            });
            it("can successfully update referenced state from format version 1", () => {
                dataStoreAttributes = {
                    pkg: '["TestDataStore1"]',
                    snapshotFormatVersion: "0.1",
                };
                updateReferencedStateTest();
            });
            it("can successfully update referenced state from format version 2", () => {
                dataStoreAttributes = {
                    pkg: '["TestDataStore1"]',
                    summaryFormatVersion: 2,
                };
                updateReferencedStateTest();
            });
            it("can successfully tombstone a remote datastore", async () => {
                dataStoreAttributes = {
                    pkg: JSON.stringify(["TestDataStore1"]),
                    isRootDataStore: false,
                };
                const buffer = (0, client_utils_1.stringToBuffer)(JSON.stringify(dataStoreAttributes), "utf8");
                const attachBlobs = new Map([
                    ["fluidDataStoreAttributes", buffer],
                ]);
                const snapshotTree = {
                    id: "dummy",
                    blobs: { [".component"]: "fluidDataStoreAttributes" },
                    trees: {},
                };
                remoteDataStoreContext = new dataStoreContext_js_1.RemoteFluidDataStoreContext({
                    id: dataStoreId,
                    snapshot: snapshotTree,
                    parentContext,
                    storage: new storageServiceWithAttachBlobs_js_1.StorageServiceWithAttachBlobs(storage, attachBlobs),
                    scope,
                    createSummarizerNodeFn,
                });
                remoteDataStoreContext.setTombstone(true);
                (0, node_assert_1.strict)(remoteDataStoreContext.tombstoned, `Local data store should be tombstoned!`);
                remoteDataStoreContext.setTombstone(false);
                (0, node_assert_1.strict)(!remoteDataStoreContext.tombstoned, `Local data store should not be tombstoned!`);
            });
        });
    });
    describe("LocalDetachedFluidDataStoreContext", () => {
        let localDataStoreContext;
        const storage = {};
        const scope = {};
        let factory;
        const makeLocallyVisibleFn = () => { };
        const channelToDataStoreFn = (fluidDataStore) => (0, dataStore_js_1.channelToDataStore)(fluidDataStore, "id", {}, (0, internal_6.createChildLogger)({ logger: parentContext.baseLogger }));
        let parentContext;
        let provideDsRuntimeWithFailingEntrypoint = false;
        beforeEach(async () => {
            const summarizerNode = (0, index_js_1.createRootSummarizerNodeWithGC)((0, internal_6.createChildLogger)(), (() => undefined), 0, 0);
            summarizerNode.startSummary(0, (0, internal_6.createChildLogger)(), 0);
            createSummarizerNodeFn = (summarizeInternal, getGCDataFn) => summarizerNode.createChild(summarizeInternal, dataStoreId, { type: internal_4.CreateSummarizerNodeSource.Local }, undefined, getGCDataFn);
            const failingEntryPoint = new internal_3.FluidObjectHandle(new internal_2.LazyPromise(async () => {
                throw new Error("Simulating failure when initializing EntryPoint");
            }), "", undefined);
            factory = {
                type: "store-type",
                get IFluidDataStoreFactory() {
                    return factory;
                },
                instantiateDataStore: async (context, existing) => provideDsRuntimeWithFailingEntrypoint
                    ? new internal_7.MockFluidDataStoreRuntime({ entryPoint: failingEntryPoint })
                    : new internal_7.MockFluidDataStoreRuntime(),
            };
            const registry = {
                get IFluidDataStoreRegistry() {
                    return registry;
                },
                get: async (pkg) => (pkg === factory.type ? factory : undefined),
            };
            parentContext = {
                IFluidDataStoreRegistry: registry,
                baseLogger: (0, internal_6.createChildLogger)(),
                clientDetails: {},
                deltaManager: new internal_7.MockDeltaManager(),
                isReadOnly: () => false,
            };
        });
        describe("Initialization", () => {
            it("rejects ids with forward slashes", async () => {
                const invalidId = "beforeSlash/afterSlash";
                const codeBlock = () => new dataStoreContext_js_1.LocalDetachedFluidDataStoreContext({
                    id: invalidId,
                    pkg: [factory.type],
                    parentContext,
                    storage,
                    scope,
                    createSummarizerNodeFn,
                    makeLocallyVisibleFn,
                    snapshotTree: undefined,
                    channelToDataStoreFn,
                });
                node_assert_1.strict.throws(codeBlock, (0, internal_7.validateAssertionError)("Data store ID contains slash"));
            });
            describe("should error on attach if data store cannot be constructed/initialized", () => {
                // Tests in this suite should be scenarios that lead to a data store which cannot be constructed for
                // some reason.
                it("because of package type for data store not present in registry", async () => {
                    let exceptionOccurred = false;
                    localDataStoreContext = new dataStoreContext_js_1.LocalDetachedFluidDataStoreContext({
                        id: dataStoreId,
                        pkg: ["some-datastore-type-not-present-in-registry"],
                        parentContext,
                        storage,
                        scope,
                        createSummarizerNodeFn,
                        makeLocallyVisibleFn,
                        snapshotTree: undefined,
                        channelToDataStoreFn,
                    });
                    const dataStore = await factory.instantiateDataStore(localDataStoreContext, false);
                    await localDataStoreContext
                        .attachRuntime(factory, dataStore)
                        .catch((error) => {
                        node_assert_1.strict.strictEqual(error.message, "Registry does not contain entry for the package", "Unexpected exception thrown");
                        exceptionOccurred = true;
                    });
                    node_assert_1.strict.strictEqual(exceptionOccurred, true, "attachRuntime() call did not fail as expected.");
                    node_assert_1.strict.strictEqual(localDataStoreContext.attachState, container_definitions_1.AttachState.Detached);
                });
                it("because of entryPoint that fails to initialize", async () => {
                    let exceptionOccurred = false;
                    provideDsRuntimeWithFailingEntrypoint = true;
                    localDataStoreContext = new dataStoreContext_js_1.LocalDetachedFluidDataStoreContext({
                        id: dataStoreId,
                        pkg: [factory.type],
                        parentContext,
                        storage,
                        scope,
                        createSummarizerNodeFn,
                        makeLocallyVisibleFn,
                        snapshotTree: undefined,
                        channelToDataStoreFn,
                    });
                    const dataStore = await factory.instantiateDataStore(localDataStoreContext, false);
                    await localDataStoreContext
                        .attachRuntime(factory, dataStore)
                        .catch((error) => {
                        node_assert_1.strict.strictEqual(error.message, "Simulating failure when initializing EntryPoint", "Unexpected exception thrown");
                        exceptionOccurred = true;
                    });
                    node_assert_1.strict.strictEqual(exceptionOccurred, true, "attachRuntime() call did not fail as expected.");
                    node_assert_1.strict.strictEqual(localDataStoreContext.attachState, container_definitions_1.AttachState.Detached);
                });
            });
        });
    });
});
//# sourceMappingURL=dataStoreContext.spec.js.map