"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const node_assert_1 = require("node:assert");
const internal_1 = require("@fluidframework/container-definitions/internal");
const internal_2 = require("@fluidframework/telemetry-utils/internal");
const internal_3 = require("@fluidframework/test-runtime-utils/internal");
const sinon_1 = __importDefault(require("sinon"));
const containerRuntime_js_1 = require("../containerRuntime.js");
const packageVersion_js_1 = require("../packageVersion.js");
const runtimeLayerCompatState_js_1 = require("../runtimeLayerCompatState.js");
const dataStoreCreationHelper_js_1 = require("./dataStoreCreationHelper.js");
// eslint-disable-next-line import-x/no-internal-modules
const gcUnitTestHelpers_js_1 = require("./gc/gcUnitTestHelpers.js");
function validateFailureProperties(error, isGenerationCompatible, incompatibleLayerGeneration, incompatibleLayer, unsupportedFeatures) {
    (0, node_assert_1.strict)((0, internal_2.isLayerIncompatibilityError)(error), "Error should be a layerIncompatibilityError");
    (0, node_assert_1.strict)(typeof error.details === "string", "Error details should be present");
    const detailedProperties = JSON.parse(error.details);
    node_assert_1.strict.strictEqual(detailedProperties.isGenerationCompatible, isGenerationCompatible, "Generation compatibility not as expected");
    node_assert_1.strict.strictEqual(error.layer, "runtime", "Layer type not as expected");
    node_assert_1.strict.strictEqual(error.incompatibleLayer, incompatibleLayer, "Incompatible layer type not as expected");
    node_assert_1.strict.strictEqual(error.layerVersion, packageVersion_js_1.pkgVersion, "Runtime version not as expected");
    node_assert_1.strict.strictEqual(detailedProperties.layerGeneration, runtimeLayerCompatState_js_1.runtimeCoreCompatDetails.generation, "Runtime generation not as expected");
    node_assert_1.strict.deepStrictEqual(detailedProperties.unsupportedFeatures, unsupportedFeatures, "Unsupported features not as expected");
    node_assert_1.strict.strictEqual(error.incompatibleLayerVersion, packageVersion_js_1.pkgVersion, `${incompatibleLayer} version not as expected`);
    node_assert_1.strict.strictEqual(detailedProperties.incompatibleLayerGeneration, incompatibleLayerGeneration, `${incompatibleLayer} generation not as expected`);
    return true;
}
async function createAndLoadRuntime(compatibilityDetails) {
    const mockContext = {
        attachState: internal_1.AttachState.Attached,
        deltaManager: new internal_3.MockDeltaManager(),
        audience: new internal_3.MockAudience(),
        quorum: new internal_3.MockQuorumClients(),
        taggedLogger: (0, internal_2.createChildLogger)({}),
        clientDetails: { capabilities: { interactive: true } },
        closeFn: (error) => { },
        updateDirtyContainerState: (_dirty) => { },
        getLoadedFromVersion: () => undefined,
        ILayerCompatDetails: compatibilityDetails,
    };
    await containerRuntime_js_1.ContainerRuntime.loadRuntime({
        context: mockContext,
        registryEntries: [],
        existing: false,
        provideEntryPoint: async () => ({
            myProp: "myValue",
        }),
    });
}
async function createAndLoadDataStore(compatibilityDetails) {
    const localDataStoreContext = (0, dataStoreCreationHelper_js_1.createLocalDataStoreContext)({}, compatibilityDetails);
    await localDataStoreContext.realize();
}
describe("Runtime Layer compatibility", () => {
    /**
     * These tests ensure that the validation logic for layer compatibility is correct
     * and has the correct error / properties.
     */
    describe("Validation error and properties", () => {
        const mc = (0, internal_2.createChildMonitoringContext)({ logger: (0, internal_2.createChildLogger)() });
        const testCases = [
            {
                layerType: "loader",
                validateCompatibility: (maybeCompatDetails, disposeFn) => (0, runtimeLayerCompatState_js_1.validateLoaderCompatibility)(maybeCompatDetails, disposeFn, mc),
                layerSupportRequirements: runtimeLayerCompatState_js_1.loaderSupportRequirementsForRuntime,
            },
            {
                layerType: "dataStore",
                validateCompatibility: (maybeCompatDetails, disposeFn) => (0, runtimeLayerCompatState_js_1.validateDatastoreCompatibility)(maybeCompatDetails, disposeFn, mc),
                layerSupportRequirements: runtimeLayerCompatState_js_1.dataStoreSupportRequirementsForRuntime,
            },
        ];
        for (const testCase of testCases) {
            const layerSupportRequirements = testCase.layerSupportRequirements;
            let originalRequiredFeatures;
            beforeEach(() => {
                originalRequiredFeatures = [...layerSupportRequirements.requiredFeatures];
            });
            afterEach(() => {
                layerSupportRequirements.requiredFeatures = [...originalRequiredFeatures];
            });
            describe(`Validate ${testCase.layerType} Compatibility`, () => {
                it(`Runtime is compatible with old ${testCase.layerType} (pre-enforcement)`, () => {
                    // Older layer will not have ILayerCompatDetails defined.
                    node_assert_1.strict.doesNotThrow(() => testCase.validateCompatibility(undefined /* maybeCompatDetails */, () => {
                        throw new Error("should not dispose");
                    }), `Runtime should be compatible with older ${testCase.layerType} layer`);
                });
                it(`Runtime generation and features are compatible with ${testCase.layerType}`, () => {
                    layerSupportRequirements.requiredFeatures = ["feature1", "feature2"];
                    const layerCompatDetails = {
                        pkgVersion: packageVersion_js_1.pkgVersion,
                        generation: layerSupportRequirements.minSupportedGeneration,
                        supportedFeatures: new Set(layerSupportRequirements.requiredFeatures),
                    };
                    node_assert_1.strict.doesNotThrow(() => testCase.validateCompatibility(layerCompatDetails, () => {
                        throw new Error("should not dispose");
                    }), `Runtime should be compatible with ${testCase.layerType} layer`);
                });
                it(`Loader generation is incompatible with ${testCase.layerType}`, () => {
                    const disposeFn = sinon_1.default.fake();
                    layerSupportRequirements.requiredFeatures = ["feature1", "feature2"];
                    const layerGeneration = layerSupportRequirements.minSupportedGeneration - 1;
                    const layerCompatDetails = {
                        pkgVersion: packageVersion_js_1.pkgVersion,
                        generation: layerGeneration,
                        supportedFeatures: new Set(layerSupportRequirements.requiredFeatures),
                    };
                    node_assert_1.strict.throws(() => testCase.validateCompatibility(layerCompatDetails, disposeFn), (e) => validateFailureProperties(e, false /* isGenerationCompatible */, layerGeneration, testCase.layerType), `Runtime should be incompatible with ${testCase.layerType} layer`);
                    (0, node_assert_1.strict)(disposeFn.calledOnce, "Dispose should be called");
                });
                it(`Runtime features are incompatible with ${testCase.layerType}`, () => {
                    const disposeFn = sinon_1.default.fake();
                    const layerGeneration = layerSupportRequirements.minSupportedGeneration;
                    const requiredFeatures = ["feature2", "feature3"];
                    layerSupportRequirements.requiredFeatures = requiredFeatures;
                    const layerCompatDetails = {
                        pkgVersion: packageVersion_js_1.pkgVersion,
                        generation: layerGeneration,
                        supportedFeatures: new Set(),
                    };
                    node_assert_1.strict.throws(() => testCase.validateCompatibility(layerCompatDetails, disposeFn), (e) => validateFailureProperties(e, true /* isGenerationCompatible */, layerGeneration, testCase.layerType, requiredFeatures), `Runtime should be incompatible with ${testCase.layerType} layer`);
                    (0, node_assert_1.strict)(disposeFn.calledOnce, "Dispose should be called");
                });
                it(`Runtime generation and features are both incompatible with ${testCase.layerType}`, () => {
                    const disposeFn = sinon_1.default.fake();
                    const layerGeneration = layerSupportRequirements.minSupportedGeneration - 1;
                    const requiredFeatures = ["feature2"];
                    layerSupportRequirements.requiredFeatures = requiredFeatures;
                    const layerCompatDetails = {
                        pkgVersion: packageVersion_js_1.pkgVersion,
                        generation: layerGeneration,
                        supportedFeatures: new Set(),
                    };
                    node_assert_1.strict.throws(() => testCase.validateCompatibility(layerCompatDetails, disposeFn), (e) => validateFailureProperties(e, false /* isGenerationCompatible */, layerGeneration, testCase.layerType, requiredFeatures), `Runtime should be incompatible with ${testCase.layerType} layer`);
                    (0, node_assert_1.strict)(disposeFn.calledOnce, "Dispose should be called");
                });
            });
        }
    });
    /**
     * These tests validates that the Loader layer compatibility is correctly enforced during load / initialization.
     */
    describe("Validation during load / initialization", () => {
        const testCases = [
            {
                layerType: "loader",
                layerSupportRequirements: runtimeLayerCompatState_js_1.loaderSupportRequirementsForRuntime,
                createAndLoad: createAndLoadRuntime,
            },
            {
                layerType: "dataStore",
                layerSupportRequirements: runtimeLayerCompatState_js_1.dataStoreSupportRequirementsForRuntime,
                createAndLoad: createAndLoadDataStore,
            },
        ];
        for (const testCase of testCases) {
            describe(`Validate ${testCase.layerType} Compatibility`, () => {
                it(`Older ${testCase.layerType} is compatible`, async () => {
                    await node_assert_1.strict.doesNotReject(async () => testCase.createAndLoad(), `Older ${testCase.layerType} should be compatible`);
                });
                it(`${testCase.layerType} with generation >= minSupportedGeneration is compatible`, async () => {
                    const layerCompatDetails = {
                        pkgVersion: packageVersion_js_1.pkgVersion,
                        generation: testCase.layerSupportRequirements.minSupportedGeneration,
                        supportedFeatures: new Set(),
                    };
                    await node_assert_1.strict.doesNotReject(async () => testCase.createAndLoad(layerCompatDetails), `${testCase.layerType} with generation >= minSupportedGeneration should be compatible`);
                });
                it(`${testCase.layerType} with generation < minSupportedGeneration is not compatible`, async () => {
                    const layerGeneration = testCase.layerSupportRequirements.minSupportedGeneration - 1;
                    const layerCompatDetails = {
                        pkgVersion: packageVersion_js_1.pkgVersion,
                        generation: layerGeneration,
                        supportedFeatures: new Set(),
                    };
                    await node_assert_1.strict.rejects(async () => testCase.createAndLoad(layerCompatDetails), (error) => validateFailureProperties(error, false /* isGenerationCompatible */, layerGeneration, testCase.layerType), `${testCase.layerType} with generation < minSupportedGeneration should be incompatible`);
                });
            });
        }
    });
    describe("DisableStrictLoaderLayerCompatibilityCheck config for missing loader compat details", () => {
        let originalMinSupportedGeneration;
        let mc;
        let logger;
        let configProvider;
        beforeEach(() => {
            const loaderSupportRequirements = runtimeLayerCompatState_js_1.loaderSupportRequirementsForRuntime;
            // Set up incompatible configuration
            originalMinSupportedGeneration = loaderSupportRequirements.minSupportedGeneration;
            loaderSupportRequirements.minSupportedGeneration = 1;
            configProvider = (0, gcUnitTestHelpers_js_1.createTestConfigProvider)();
            logger = new internal_2.MockLogger();
            mc = (0, internal_2.mixinMonitoringContext)(logger.toTelemetryLogger(), configProvider);
        });
        afterEach(() => {
            // Restore original configuration
            const loaderSupportRequirements = runtimeLayerCompatState_js_1.loaderSupportRequirementsForRuntime;
            loaderSupportRequirements.minSupportedGeneration = originalMinSupportedGeneration;
        });
        it("DisableStrictLoaderLayerCompatibilityCheck = undefined (default) should fail validation", () => {
            const disposeFn = sinon_1.default.fake();
            node_assert_1.strict.throws(() => (0, runtimeLayerCompatState_js_1.validateLoaderCompatibility)(undefined /* maybeCompatDetails */, disposeFn, mc), (error) => (0, internal_2.isLayerIncompatibilityError)(error), "Should throw LayerIncompatibilityError when loader compat details are missing and strict check is enabled");
            (0, node_assert_1.strict)(disposeFn.calledOnce, "Dispose should be called");
            logger.assertMatch([
                {
                    eventName: "LayerIncompatibilityError",
                },
            ]);
        });
        it("DisableStrictLoaderLayerCompatibilityCheck = false should fail validation", () => {
            configProvider.set(runtimeLayerCompatState_js_1.disableStrictLoaderLayerCompatibilityCheckKey, false);
            const disposeFn = sinon_1.default.fake();
            node_assert_1.strict.throws(() => (0, runtimeLayerCompatState_js_1.validateLoaderCompatibility)(undefined /* maybeCompatDetails */, disposeFn, mc), (error) => (0, internal_2.isLayerIncompatibilityError)(error), "Should throw LayerIncompatibilityError when loader compat details are missing and strict check is enabled");
            (0, node_assert_1.strict)(disposeFn.calledOnce, "Dispose should be called");
            logger.assertMatch([
                {
                    eventName: "LayerIncompatibilityError",
                },
            ]);
        });
        it("DisableStrictLoaderLayerCompatibilityCheck = true should skip validation", () => {
            configProvider.set(runtimeLayerCompatState_js_1.disableStrictLoaderLayerCompatibilityCheckKey, true);
            const disposeFn = sinon_1.default.fake();
            node_assert_1.strict.doesNotThrow(() => (0, runtimeLayerCompatState_js_1.validateLoaderCompatibility)(undefined /* maybeCompatDetails */, disposeFn, mc), "Should not throw when loader compat details are missing and strict check is enabled");
            (0, node_assert_1.strict)(disposeFn.notCalled, "Dispose should not be called");
            logger.assertMatch([
                {
                    eventName: "LayerIncompatibilityDetectedButBypassed",
                },
            ]);
        });
    });
});
//# sourceMappingURL=runtimeLayerCompatValidation.spec.js.map