@fluid-experimental/odsp-end-to-end-tests 2.0.0-dev-rc.1.0.0.224419

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc.cjs ADDED
@@ -0,0 +1,15 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ module.exports = {
6
+ extends: [require.resolve("@fluidframework/eslint-config-fluid"), "prettier"],
7
+ rules: {
8
+ "prefer-arrow-callback": "off",
9
+ "@typescript-eslint/strict-boolean-expressions": "off", // requires strictNullChecks=true in tsconfig
10
+ "import/namespace": "off",
11
+ },
12
+ parserOptions: {
13
+ project: ["./src/test/tsconfig.json"],
14
+ },
15
+ };
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @fluid-experimental/odsp-end-to-end-tests
2
+
3
+ ## 2.0.0-internal.8.0.0
4
+
5
+ Dependency updates only.
6
+
7
+ ## 2.0.0-internal.7.4.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) Microsoft Corporation and contributors. All rights reserved.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # @fluid-experimental/odsp-end-to-end-tests
2
+
3
+ Functional end-to-end tests for ODSP using RaaS. Here we evaluate various scenarios that involves odsp-client, IFluidContainer and a range of distributed data structures (DDSes).
4
+
5
+ To run ODSP using RaaS e2e tests: `test:realsvc:odsp:run`.
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createOdspClient = void 0;
4
+ const odsp_client_1 = require("@fluid-experimental/odsp-client");
5
+ const telemetry_utils_1 = require("@fluidframework/telemetry-utils");
6
+ const OdspTokenFactory_1 = require("./OdspTokenFactory");
7
+ /**
8
+ * This function will determine if local or remote mode is required (based on FLUID_CLIENT), and return a new
9
+ * {@link OdspClient} instance based on the mode by setting the Connection config accordingly.
10
+ */
11
+ function createOdspClient(creds, logger, configProvider) {
12
+ const siteUrl = process.env.odsp__client__siteUrl;
13
+ const driveId = process.env.odsp__client__driveId;
14
+ const clientId = process.env.odsp__client__client__id;
15
+ const clientSecret = process.env.odsp__client__client__secret;
16
+ if (siteUrl === "" || siteUrl === undefined) {
17
+ throw new Error("site url is missing");
18
+ }
19
+ if (driveId === "" || driveId === undefined) {
20
+ throw new Error("RaaS drive id is missing");
21
+ }
22
+ if (clientId === "" || clientId === undefined) {
23
+ throw new Error("client id is missing");
24
+ }
25
+ if (clientSecret === "" || clientSecret === undefined) {
26
+ throw new Error("client secret is missing");
27
+ }
28
+ if (creds.username === undefined || creds.password === undefined) {
29
+ throw new Error("username or password is missing for login account");
30
+ }
31
+ const credentials = {
32
+ clientId,
33
+ clientSecret,
34
+ ...creds,
35
+ };
36
+ const connectionProps = {
37
+ siteUrl,
38
+ tokenProvider: new OdspTokenFactory_1.OdspTestTokenProvider(credentials),
39
+ driveId,
40
+ };
41
+ const getLogger = () => {
42
+ const testLogger = getTestLogger?.();
43
+ if (!logger && !testLogger) {
44
+ return undefined;
45
+ }
46
+ if (logger && testLogger) {
47
+ return (0, telemetry_utils_1.createMultiSinkLogger)({ loggers: [logger, testLogger] });
48
+ }
49
+ return logger ?? testLogger;
50
+ };
51
+ return new odsp_client_1.OdspClient({
52
+ connection: connectionProps,
53
+ logger: getLogger(),
54
+ configProvider,
55
+ });
56
+ }
57
+ exports.createOdspClient = createOdspClient;
58
+ //# sourceMappingURL=OdspClientFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OdspClientFactory.js","sourceRoot":"","sources":["../../src/test/OdspClientFactory.ts"],"names":[],"mappings":";;;AAKA,iEAAmF;AAEnF,qEAAoF;AACpF,yDAA2D;AAmB3D;;;GAGG;AACH,SAAgB,gBAAgB,CAC/B,KAA4B,EAC5B,MAAmB,EACnB,cAAoC;IAEpC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,qBAA+B,CAAC;IAC5D,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,qBAA+B,CAAC;IAC5D,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAkC,CAAC;IAChE,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,4BAAsC,CAAC;IACxE,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,SAAS,EAAE;QAC5C,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;KACvC;IACD,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,SAAS,EAAE;QAC5C,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;KAC5C;IAED,IAAI,QAAQ,KAAK,EAAE,IAAI,QAAQ,KAAK,SAAS,EAAE;QAC9C,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;KACxC;IAED,IAAI,YAAY,KAAK,EAAE,IAAI,YAAY,KAAK,SAAS,EAAE;QACtD,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;KAC5C;IAED,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS,EAAE;QACjE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;KACrE;IAED,MAAM,WAAW,GAAqB;QACrC,QAAQ;QACR,YAAY;QACZ,GAAG,KAAK;KACR,CAAC;IAEF,MAAM,eAAe,GAAyB;QAC7C,OAAO;QACP,aAAa,EAAE,IAAI,wCAAqB,CAAC,WAAW,CAAC;QACrD,OAAO;KACP,CAAC;IACF,MAAM,SAAS,GAAG,GAAqC,EAAE;QACxD,MAAM,UAAU,GAAG,aAAa,EAAE,EAAE,CAAC;QACrC,IAAI,CAAC,MAAM,IAAI,CAAC,UAAU,EAAE;YAC3B,OAAO,SAAS,CAAC;SACjB;QACD,IAAI,MAAM,IAAI,UAAU,EAAE;YACzB,OAAO,IAAA,uCAAqB,EAAC,EAAE,OAAO,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC,CAAC;SAChE;QACD,OAAO,MAAM,IAAI,UAAU,CAAC;IAC7B,CAAC,CAAC;IACF,OAAO,IAAI,wBAAU,CAAC;QACrB,UAAU,EAAE,eAAe;QAC3B,MAAM,EAAE,SAAS,EAAE;QACnB,cAAc;KACd,CAAC,CAAC;AACJ,CAAC;AAtDD,4CAsDC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\nimport { IConfigProviderBase, type ITelemetryBaseLogger } from \"@fluidframework/core-interfaces\";\nimport { OdspClient, OdspConnectionConfig } from \"@fluid-experimental/odsp-client\";\n\nimport { MockLogger, createMultiSinkLogger } from \"@fluidframework/telemetry-utils\";\nimport { OdspTestTokenProvider } from \"./OdspTokenFactory\";\n\n/**\n * Interface representing the odsp-client login account credentials.\n */\nexport interface IOdspLoginCredentials {\n\tusername: string;\n\tpassword: string;\n}\n\n/**\n * Interface representing extended credentials for odsp-client, including AAD information.\n * Extends the basic login credentials with client ID and client secret.\n */\nexport interface IOdspCredentials extends IOdspLoginCredentials {\n\tclientId: string;\n\tclientSecret: string;\n}\n\n/**\n * This function will determine if local or remote mode is required (based on FLUID_CLIENT), and return a new\n * {@link OdspClient} instance based on the mode by setting the Connection config accordingly.\n */\nexport function createOdspClient(\n\tcreds: IOdspLoginCredentials,\n\tlogger?: MockLogger,\n\tconfigProvider?: IConfigProviderBase,\n): OdspClient {\n\tconst siteUrl = process.env.odsp__client__siteUrl as string;\n\tconst driveId = process.env.odsp__client__driveId as string;\n\tconst clientId = process.env.odsp__client__client__id as string;\n\tconst clientSecret = process.env.odsp__client__client__secret as string;\n\tif (siteUrl === \"\" || siteUrl === undefined) {\n\t\tthrow new Error(\"site url is missing\");\n\t}\n\tif (driveId === \"\" || driveId === undefined) {\n\t\tthrow new Error(\"RaaS drive id is missing\");\n\t}\n\n\tif (clientId === \"\" || clientId === undefined) {\n\t\tthrow new Error(\"client id is missing\");\n\t}\n\n\tif (clientSecret === \"\" || clientSecret === undefined) {\n\t\tthrow new Error(\"client secret is missing\");\n\t}\n\n\tif (creds.username === undefined || creds.password === undefined) {\n\t\tthrow new Error(\"username or password is missing for login account\");\n\t}\n\n\tconst credentials: IOdspCredentials = {\n\t\tclientId,\n\t\tclientSecret,\n\t\t...creds,\n\t};\n\n\tconst connectionProps: OdspConnectionConfig = {\n\t\tsiteUrl,\n\t\ttokenProvider: new OdspTestTokenProvider(credentials),\n\t\tdriveId,\n\t};\n\tconst getLogger = (): ITelemetryBaseLogger | undefined => {\n\t\tconst testLogger = getTestLogger?.();\n\t\tif (!logger && !testLogger) {\n\t\t\treturn undefined;\n\t\t}\n\t\tif (logger && testLogger) {\n\t\t\treturn createMultiSinkLogger({ loggers: [logger, testLogger] });\n\t\t}\n\t\treturn logger ?? testLogger;\n\t};\n\treturn new OdspClient({\n\t\tconnection: connectionProps,\n\t\tlogger: getLogger(),\n\t\tconfigProvider,\n\t});\n}\n"]}
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OdspTestTokenProvider = void 0;
4
+ const odsp_doclib_utils_1 = require("@fluidframework/odsp-doclib-utils");
5
+ /**
6
+ * This class implements the IOdspTokenProvider interface and provides methods for fetching push and storage tokens.
7
+ */
8
+ class OdspTestTokenProvider {
9
+ constructor(credentials) {
10
+ this.creds = credentials;
11
+ }
12
+ async fetchWebsocketToken(siteUrl, refresh) {
13
+ const pushScope = "offline_access https://pushchannel.1drv.ms/PushChannel.ReadWrite.All";
14
+ const tokens = await this.fetchTokens(siteUrl, pushScope);
15
+ return {
16
+ fromCache: true,
17
+ token: tokens.accessToken,
18
+ };
19
+ }
20
+ async fetchStorageToken(siteUrl, refresh) {
21
+ const sharePointScopes = `${siteUrl}/Container.Selected`;
22
+ const tokens = await this.fetchTokens(siteUrl, sharePointScopes);
23
+ return {
24
+ fromCache: true,
25
+ token: tokens.accessToken,
26
+ };
27
+ }
28
+ async fetchTokens(siteUrl, scope) {
29
+ const server = new URL(siteUrl).host;
30
+ const clientConfig = {
31
+ clientId: this.creds.clientId,
32
+ clientSecret: this.creds.clientSecret,
33
+ };
34
+ const credentials = {
35
+ grant_type: "password",
36
+ username: this.creds.username,
37
+ password: this.creds.password,
38
+ };
39
+ const body = {
40
+ scope,
41
+ client_id: clientConfig.clientId,
42
+ client_secret: clientConfig.clientSecret,
43
+ ...credentials,
44
+ };
45
+ const response = await (0, odsp_doclib_utils_1.unauthPostAsync)((0, odsp_doclib_utils_1.getFetchTokenUrl)(server), new URLSearchParams(body));
46
+ const parsedResponse = await response.json();
47
+ const accessToken = parsedResponse.access_token;
48
+ const refreshToken = parsedResponse.refresh_token;
49
+ return { accessToken, refreshToken };
50
+ }
51
+ }
52
+ exports.OdspTestTokenProvider = OdspTestTokenProvider;
53
+ //# sourceMappingURL=OdspTokenFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OdspTokenFactory.js","sourceRoot":"","sources":["../../src/test/OdspTokenFactory.ts"],"names":[],"mappings":";;;AAOA,yEAK2C;AAI3C;;GAEG;AACH,MAAa,qBAAqB;IAGjC,YAAY,WAA6B;QACxC,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC;IAC1B,CAAC;IAEM,KAAK,CAAC,mBAAmB,CAAC,OAAe,EAAE,OAAgB;QACjE,MAAM,SAAS,GAAG,sEAAsE,CAAC;QACzF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAC1D,OAAO;YACN,SAAS,EAAE,IAAI;YACf,KAAK,EAAE,MAAM,CAAC,WAAW;SACzB,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,iBAAiB,CAAC,OAAe,EAAE,OAAgB;QAC/D,MAAM,gBAAgB,GAAG,GAAG,OAAO,qBAAqB,CAAC;QACzD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;QACjE,OAAO;YACN,SAAS,EAAE,IAAI;YACf,KAAK,EAAE,MAAM,CAAC,WAAW;SACzB,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,WAAW,CACxB,OAAe,EACf,KAAa;QAKb,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC;QACrC,MAAM,YAAY,GAAkB;YACnC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ;YAC7B,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY;SACrC,CAAC;QACF,MAAM,WAAW,GAA4B;YAC5C,UAAU,EAAE,UAAU;YACtB,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ;YAC7B,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ;SAC7B,CAAC;QACF,MAAM,IAAI,GAAG;YACZ,KAAK;YACL,SAAS,EAAE,YAAY,CAAC,QAAQ;YAChC,aAAa,EAAE,YAAY,CAAC,YAAY;YACxC,GAAG,WAAW;SACd,CAAC;QACF,MAAM,QAAQ,GAAG,MAAM,IAAA,mCAAe,EAAC,IAAA,oCAAgB,EAAC,MAAM,CAAC,EAAE,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;QAE5F,MAAM,cAAc,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QAC7C,MAAM,WAAW,GAAG,cAAc,CAAC,YAAY,CAAC;QAChD,MAAM,YAAY,GAAG,cAAc,CAAC,aAAa,CAAC;QAElD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC;IACtC,CAAC;CACD;AAxDD,sDAwDC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n/* eslint-disable @typescript-eslint/no-unsafe-assignment */\n/* eslint-disable @typescript-eslint/no-unsafe-member-access */\nimport { TokenResponse } from \"@fluidframework/odsp-driver-definitions\";\nimport {\n\tIClientConfig,\n\tTokenRequestCredentials,\n\tgetFetchTokenUrl,\n\tunauthPostAsync,\n} from \"@fluidframework/odsp-doclib-utils\";\nimport { IOdspTokenProvider } from \"@fluid-experimental/odsp-client\";\nimport { IOdspCredentials } from \"./OdspClientFactory\";\n\n/**\n * This class implements the IOdspTokenProvider interface and provides methods for fetching push and storage tokens.\n */\nexport class OdspTestTokenProvider implements IOdspTokenProvider {\n\tprivate readonly creds: IOdspCredentials;\n\n\tconstructor(credentials: IOdspCredentials) {\n\t\tthis.creds = credentials;\n\t}\n\n\tpublic async fetchWebsocketToken(siteUrl: string, refresh: boolean): Promise<TokenResponse> {\n\t\tconst pushScope = \"offline_access https://pushchannel.1drv.ms/PushChannel.ReadWrite.All\";\n\t\tconst tokens = await this.fetchTokens(siteUrl, pushScope);\n\t\treturn {\n\t\t\tfromCache: true,\n\t\t\ttoken: tokens.accessToken,\n\t\t};\n\t}\n\n\tpublic async fetchStorageToken(siteUrl: string, refresh: boolean): Promise<TokenResponse> {\n\t\tconst sharePointScopes = `${siteUrl}/Container.Selected`;\n\t\tconst tokens = await this.fetchTokens(siteUrl, sharePointScopes);\n\t\treturn {\n\t\t\tfromCache: true,\n\t\t\ttoken: tokens.accessToken,\n\t\t};\n\t}\n\n\tprivate async fetchTokens(\n\t\tsiteUrl: string,\n\t\tscope: string,\n\t): Promise<{\n\t\taccessToken: string;\n\t\trefreshToken: string;\n\t}> {\n\t\tconst server = new URL(siteUrl).host;\n\t\tconst clientConfig: IClientConfig = {\n\t\t\tclientId: this.creds.clientId,\n\t\t\tclientSecret: this.creds.clientSecret,\n\t\t};\n\t\tconst credentials: TokenRequestCredentials = {\n\t\t\tgrant_type: \"password\",\n\t\t\tusername: this.creds.username,\n\t\t\tpassword: this.creds.password,\n\t\t};\n\t\tconst body = {\n\t\t\tscope,\n\t\t\tclient_id: clientConfig.clientId,\n\t\t\tclient_secret: clientConfig.clientSecret,\n\t\t\t...credentials,\n\t\t};\n\t\tconst response = await unauthPostAsync(getFetchTokenUrl(server), new URLSearchParams(body));\n\n\t\tconst parsedResponse = await response.json();\n\t\tconst accessToken = parsedResponse.access_token;\n\t\tconst refreshToken = parsedResponse.refresh_token;\n\n\t\treturn { accessToken, refreshToken };\n\t}\n}\n"]}
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CounterTestDataObject = exports.TestDataObject = void 0;
4
+ /*!
5
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
6
+ * Licensed under the MIT License.
7
+ */
8
+ const aqueduct_1 = require("@fluidframework/aqueduct");
9
+ const counter_1 = require("@fluidframework/counter");
10
+ class TestDataObject extends aqueduct_1.DataObject {
11
+ constructor(props) {
12
+ super(props);
13
+ }
14
+ }
15
+ exports.TestDataObject = TestDataObject;
16
+ TestDataObject.Name = "@fluid-example/test-data-object";
17
+ TestDataObject.factory = new aqueduct_1.DataObjectFactory(TestDataObject.Name, TestDataObject, [], {});
18
+ class CounterTestDataObject extends aqueduct_1.DataObject {
19
+ /**
20
+ * Do setup work here
21
+ */
22
+ async initializingFirstTime() {
23
+ const counter = counter_1.SharedCounter.create(this.runtime);
24
+ this.root.set("counter-key", counter.handle);
25
+ }
26
+ async hasInitialized() {
27
+ const counterHandle = this.root.get("counter-key");
28
+ this._counter = await counterHandle?.get();
29
+ }
30
+ increment() {
31
+ this.counter.increment(1);
32
+ }
33
+ get value() {
34
+ return this.counter.value;
35
+ }
36
+ get counter() {
37
+ if (this._counter === undefined) {
38
+ throw new Error("SharedCounter not initialized");
39
+ }
40
+ return this._counter;
41
+ }
42
+ }
43
+ exports.CounterTestDataObject = CounterTestDataObject;
44
+ CounterTestDataObject.Name = "@fluid-example/counter-test-data-object";
45
+ CounterTestDataObject.factory = new aqueduct_1.DataObjectFactory(CounterTestDataObject.Name, CounterTestDataObject, [counter_1.SharedCounter.getFactory()], {});
46
+ //# sourceMappingURL=TestDataObject.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestDataObject.js","sourceRoot":"","sources":["../../src/test/TestDataObject.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACH,uDAA2F;AAE3F,qDAAwD;AAExD,MAAa,cAAe,SAAQ,qBAAU;IAU7C,YAAY,KAAuB;QAClC,KAAK,CAAC,KAAK,CAAC,CAAC;IACd,CAAC;;AAZF,wCAaC;AAZuB,mBAAI,GAAG,iCAAiC,CAAC;AAEzC,sBAAO,GAAG,IAAI,4BAAiB,CACrD,cAAc,CAAC,IAAI,EACnB,cAAc,EACd,EAAE,EACF,EAAE,CACF,CAAC;AAOH,MAAa,qBAAsB,SAAQ,qBAAU;IAGpD;;OAEG;IACO,KAAK,CAAC,qBAAqB;QACpC,MAAM,OAAO,GAAG,uBAAa,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C,CAAC;IAES,KAAK,CAAC,cAAc;QAC7B,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAA8B,aAAa,CAAC,CAAC;QAChF,IAAI,CAAC,QAAQ,GAAG,MAAM,aAAa,EAAE,GAAG,EAAE,CAAC;IAC5C,CAAC;IAWM,SAAS;QACf,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAC3B,CAAC;IAED,IAAW,KAAK;QACf,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;IAC3B,CAAC;IAED,IAAY,OAAO;QAClB,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE;YAChC,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;SACjD;QACD,OAAO,IAAI,CAAC,QAAQ,CAAC;IACtB,CAAC;;AAtCF,sDAuCC;AAvBuB,0BAAI,GAAG,yCAAyC,CAAC;AAEjD,6BAAO,GAAG,IAAI,4BAAiB,CACrD,qBAAqB,CAAC,IAAI,EAC1B,qBAAqB,EACrB,CAAC,uBAAa,CAAC,UAAU,EAAE,CAAC,EAC5B,EAAE,CACF,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\nimport { DataObject, DataObjectFactory, IDataObjectProps } from \"@fluidframework/aqueduct\";\nimport { IFluidHandle } from \"@fluidframework/core-interfaces\";\nimport { SharedCounter } from \"@fluidframework/counter\";\n\nexport class TestDataObject extends DataObject {\n\tpublic static readonly Name = \"@fluid-example/test-data-object\";\n\n\tpublic static readonly factory = new DataObjectFactory(\n\t\tTestDataObject.Name,\n\t\tTestDataObject,\n\t\t[],\n\t\t{},\n\t);\n\n\tconstructor(props: IDataObjectProps) {\n\t\tsuper(props);\n\t}\n}\n\nexport class CounterTestDataObject extends DataObject {\n\tprivate _counter: SharedCounter | undefined;\n\n\t/**\n\t * Do setup work here\n\t */\n\tprotected async initializingFirstTime(): Promise<void> {\n\t\tconst counter = SharedCounter.create(this.runtime);\n\t\tthis.root.set(\"counter-key\", counter.handle);\n\t}\n\n\tprotected async hasInitialized(): Promise<void> {\n\t\tconst counterHandle = this.root.get<IFluidHandle<SharedCounter>>(\"counter-key\");\n\t\tthis._counter = await counterHandle?.get();\n\t}\n\n\tpublic static readonly Name = \"@fluid-example/counter-test-data-object\";\n\n\tpublic static readonly factory = new DataObjectFactory(\n\t\tCounterTestDataObject.Name,\n\t\tCounterTestDataObject,\n\t\t[SharedCounter.getFactory()],\n\t\t{},\n\t);\n\n\tpublic increment(): void {\n\t\tthis.counter.increment(1);\n\t}\n\n\tpublic get value(): number {\n\t\treturn this.counter.value;\n\t}\n\n\tprivate get counter(): SharedCounter {\n\t\tif (this._counter === undefined) {\n\t\t\tthrow new Error(\"SharedCounter not initialized\");\n\t\t}\n\t\treturn this._counter;\n\t}\n}\n"]}
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /*!
4
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
5
+ * Licensed under the MIT License.
6
+ */
7
+ const node_assert_1 = require("node:assert");
8
+ const container_definitions_1 = require("@fluidframework/container-definitions");
9
+ const map_1 = require("@fluidframework/map");
10
+ const test_utils_1 = require("@fluidframework/test-utils");
11
+ const container_loader_1 = require("@fluidframework/container-loader");
12
+ const OdspClientFactory_1 = require("./OdspClientFactory");
13
+ const utils_1 = require("./utils");
14
+ const configProvider = (settings) => ({
15
+ getRawConfig: (name) => settings[name],
16
+ });
17
+ describe("Fluid audience", () => {
18
+ const connectTimeoutMs = 10000;
19
+ let client;
20
+ let schema;
21
+ const client1Creds = {
22
+ username: process.env.odsp__client__login__username,
23
+ password: process.env.odsp__client__login__password,
24
+ };
25
+ const client2Creds = {
26
+ username: process.env.odsp__client2__login__username,
27
+ password: process.env.odsp__client2__login__password,
28
+ };
29
+ beforeEach(() => {
30
+ client = (0, OdspClientFactory_1.createOdspClient)(client1Creds);
31
+ schema = {
32
+ initialObjects: {
33
+ map1: map_1.SharedMap,
34
+ },
35
+ };
36
+ });
37
+ /**
38
+ * Scenario: Find original member/self
39
+ *
40
+ * Expected behavior: container should have a single member upon creation.
41
+ */
42
+ it("can find original member", async () => {
43
+ const { container, services } = await client.createContainer(schema);
44
+ const itemId = await container.attach();
45
+ if (container.connectionState !== container_loader_1.ConnectionState.Connected) {
46
+ await (0, test_utils_1.timeoutPromise)((resolve) => container.once("connected", () => resolve()), {
47
+ durationMs: connectTimeoutMs,
48
+ errorMsg: "container connect() timeout",
49
+ });
50
+ }
51
+ node_assert_1.strict.strictEqual(typeof itemId, "string", "Attach did not return a string ID");
52
+ node_assert_1.strict.strictEqual(container.attachState, container_definitions_1.AttachState.Attached, "Container is not attached after attach is called");
53
+ /* This is a workaround for a known bug, we should have one member (self) upon container connection */
54
+ const myself = await (0, utils_1.waitForMember)(services.audience, client1Creds.username);
55
+ node_assert_1.strict.notStrictEqual(myself, undefined, "We should have myself at this point.");
56
+ const members = services.audience.getMembers();
57
+ node_assert_1.strict.strictEqual(members.size, 1, "We should have only one member at this point.");
58
+ });
59
+ /**
60
+ * Scenario: Find partner member
61
+ *
62
+ * Expected behavior: upon resolving container, the partner member should be able
63
+ * to resolve original member.
64
+ *
65
+ * Note: This test is currently skipped because the web app examples indicate the audience is functioning properly. AB#6425
66
+ */
67
+ it.skip("can find partner member", async () => {
68
+ const { container, services } = await client.createContainer(schema);
69
+ const itemId = await container.attach();
70
+ if (container.connectionState !== container_loader_1.ConnectionState.Connected) {
71
+ await (0, test_utils_1.timeoutPromise)((resolve) => container.once("connected", () => resolve()), {
72
+ durationMs: connectTimeoutMs,
73
+ errorMsg: "container connect() timeout",
74
+ });
75
+ }
76
+ node_assert_1.strict.strictEqual(typeof itemId, "string", "Attach did not return a string ID");
77
+ node_assert_1.strict.strictEqual(container.attachState, container_definitions_1.AttachState.Attached, "Container is not attached after attach is called");
78
+ /* This is a workaround for a known bug, we should have one member (self) upon container connection */
79
+ const originalSelf = await (0, utils_1.waitForMember)(services.audience, client1Creds.username);
80
+ node_assert_1.strict.notStrictEqual(originalSelf, undefined, "We should have myself at this point.");
81
+ // pass client2 credentials
82
+ const client2 = (0, OdspClientFactory_1.createOdspClient)(client2Creds, undefined, configProvider({
83
+ "Fluid.Container.ForceWriteConnection": true,
84
+ }));
85
+ const { services: servicesGet } = await client2.getContainer(itemId, schema);
86
+ /* This is a workaround for a known bug, we should have one member (self) upon container connection */
87
+ const partner = await (0, utils_1.waitForMember)(servicesGet.audience, client2Creds.username);
88
+ node_assert_1.strict.notStrictEqual(partner, undefined, "We should have partner at this point.");
89
+ const members = servicesGet.audience.getMembers();
90
+ node_assert_1.strict.strictEqual(members.size, 2, "We should have two members at this point.");
91
+ node_assert_1.strict.notStrictEqual(partner?.userId, originalSelf?.userId, "Self and partner should have different IDs");
92
+ });
93
+ /**
94
+ * Scenario: Partner should be able to observe change in audience
95
+ *
96
+ * Expected behavior: upon 1 partner leaving, other parther should observe
97
+ * memberRemoved event and have correct partner count.
98
+ *
99
+ * Note: This test is currently skipped because the web app examples indicate the audience is functioning properly. AB#6425
100
+ */
101
+ it.skip("can observe member leaving", async () => {
102
+ const { container } = await client.createContainer(schema);
103
+ const itemId = await container.attach();
104
+ if (container.connectionState !== container_loader_1.ConnectionState.Connected) {
105
+ await (0, test_utils_1.timeoutPromise)((resolve) => container.once("connected", () => resolve()), {
106
+ durationMs: connectTimeoutMs,
107
+ errorMsg: "container connect() timeout",
108
+ });
109
+ }
110
+ // pass client2 siteUrl and driveId
111
+ const client2 = (0, OdspClientFactory_1.createOdspClient)(client2Creds, undefined, configProvider({
112
+ "Fluid.Container.ForceWriteConnection": true,
113
+ }));
114
+ const { services: servicesGet } = await client2.getContainer(itemId, schema);
115
+ /* This is a workaround for a known bug, we should have one member (self) upon container connection */
116
+ const partner = await (0, utils_1.waitForMember)(servicesGet.audience, client2Creds.username);
117
+ node_assert_1.strict.notStrictEqual(partner, undefined, "We should have partner at this point.");
118
+ let members = servicesGet.audience.getMembers();
119
+ node_assert_1.strict.strictEqual(members.size, 2, "We should have two members at this point.");
120
+ container.disconnect();
121
+ await new Promise((resolve) => {
122
+ servicesGet.audience.on("memberRemoved", () => {
123
+ resolve();
124
+ });
125
+ });
126
+ members = servicesGet.audience.getMembers();
127
+ node_assert_1.strict.strictEqual(members.size, 1, "We should have one member left at this point.");
128
+ });
129
+ });
130
+ //# sourceMappingURL=audience.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audience.spec.js","sourceRoot":"","sources":["../../src/test/audience.spec.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\nimport { strict as assert } from \"node:assert\";\n\nimport { OdspClient } from \"@fluid-experimental/odsp-client\";\nimport { AttachState } from \"@fluidframework/container-definitions\";\nimport { ContainerSchema } from \"@fluidframework/fluid-static\";\nimport { SharedMap } from \"@fluidframework/map\";\nimport { timeoutPromise } from \"@fluidframework/test-utils\";\n\nimport { ConnectionState } from \"@fluidframework/container-loader\";\nimport { ConfigTypes, IConfigProviderBase } from \"@fluidframework/core-interfaces\";\nimport { createOdspClient, IOdspLoginCredentials } from \"./OdspClientFactory\";\nimport { waitForMember } from \"./utils\";\n\nconst configProvider = (settings: Record<string, ConfigTypes>): IConfigProviderBase => ({\n\tgetRawConfig: (name: string): ConfigTypes => settings[name],\n});\n\ndescribe(\"Fluid audience\", () => {\n\tconst connectTimeoutMs = 10_000;\n\tlet client: OdspClient;\n\tlet schema: ContainerSchema;\n\tconst client1Creds: IOdspLoginCredentials = {\n\t\tusername: process.env.odsp__client__login__username as string,\n\t\tpassword: process.env.odsp__client__login__password as string,\n\t};\n\n\tconst client2Creds: IOdspLoginCredentials = {\n\t\tusername: process.env.odsp__client2__login__username as string,\n\t\tpassword: process.env.odsp__client2__login__password as string,\n\t};\n\n\tbeforeEach(() => {\n\t\tclient = createOdspClient(client1Creds);\n\t\tschema = {\n\t\t\tinitialObjects: {\n\t\t\t\tmap1: SharedMap,\n\t\t\t},\n\t\t};\n\t});\n\n\t/**\n\t * Scenario: Find original member/self\n\t *\n\t * Expected behavior: container should have a single member upon creation.\n\t */\n\tit(\"can find original member\", async () => {\n\t\tconst { container, services } = await client.createContainer(schema);\n\t\tconst itemId = await container.attach();\n\n\t\tif (container.connectionState !== ConnectionState.Connected) {\n\t\t\tawait timeoutPromise((resolve) => container.once(\"connected\", () => resolve()), {\n\t\t\t\tdurationMs: connectTimeoutMs,\n\t\t\t\terrorMsg: \"container connect() timeout\",\n\t\t\t});\n\t\t}\n\n\t\tassert.strictEqual(typeof itemId, \"string\", \"Attach did not return a string ID\");\n\t\tassert.strictEqual(\n\t\t\tcontainer.attachState,\n\t\t\tAttachState.Attached,\n\t\t\t\"Container is not attached after attach is called\",\n\t\t);\n\n\t\t/* This is a workaround for a known bug, we should have one member (self) upon container connection */\n\t\tconst myself = await waitForMember(services.audience, client1Creds.username);\n\t\tassert.notStrictEqual(myself, undefined, \"We should have myself at this point.\");\n\n\t\tconst members = services.audience.getMembers();\n\t\tassert.strictEqual(members.size, 1, \"We should have only one member at this point.\");\n\t});\n\n\t/**\n\t * Scenario: Find partner member\n\t *\n\t * Expected behavior: upon resolving container, the partner member should be able\n\t * to resolve original member.\n\t *\n\t * Note: This test is currently skipped because the web app examples indicate the audience is functioning properly. AB#6425\n\t */\n\tit.skip(\"can find partner member\", async () => {\n\t\tconst { container, services } = await client.createContainer(schema);\n\t\tconst itemId = await container.attach();\n\n\t\tif (container.connectionState !== ConnectionState.Connected) {\n\t\t\tawait timeoutPromise((resolve) => container.once(\"connected\", () => resolve()), {\n\t\t\t\tdurationMs: connectTimeoutMs,\n\t\t\t\terrorMsg: \"container connect() timeout\",\n\t\t\t});\n\t\t}\n\n\t\tassert.strictEqual(typeof itemId, \"string\", \"Attach did not return a string ID\");\n\t\tassert.strictEqual(\n\t\t\tcontainer.attachState,\n\t\t\tAttachState.Attached,\n\t\t\t\"Container is not attached after attach is called\",\n\t\t);\n\n\t\t/* This is a workaround for a known bug, we should have one member (self) upon container connection */\n\t\tconst originalSelf = await waitForMember(services.audience, client1Creds.username);\n\t\tassert.notStrictEqual(originalSelf, undefined, \"We should have myself at this point.\");\n\n\t\t// pass client2 credentials\n\t\tconst client2 = createOdspClient(\n\t\t\tclient2Creds,\n\t\t\tundefined,\n\t\t\tconfigProvider({\n\t\t\t\t\"Fluid.Container.ForceWriteConnection\": true,\n\t\t\t}),\n\t\t);\n\t\tconst { services: servicesGet } = await client2.getContainer(itemId, schema);\n\n\t\t/* This is a workaround for a known bug, we should have one member (self) upon container connection */\n\t\tconst partner = await waitForMember(servicesGet.audience, client2Creds.username);\n\t\tassert.notStrictEqual(partner, undefined, \"We should have partner at this point.\");\n\n\t\tconst members = servicesGet.audience.getMembers();\n\t\tassert.strictEqual(members.size, 2, \"We should have two members at this point.\");\n\n\t\tassert.notStrictEqual(\n\t\t\tpartner?.userId,\n\t\t\toriginalSelf?.userId,\n\t\t\t\"Self and partner should have different IDs\",\n\t\t);\n\t});\n\n\t/**\n\t * Scenario: Partner should be able to observe change in audience\n\t *\n\t * Expected behavior: upon 1 partner leaving, other parther should observe\n\t * memberRemoved event and have correct partner count.\n\t *\n\t * Note: This test is currently skipped because the web app examples indicate the audience is functioning properly. AB#6425\n\t */\n\tit.skip(\"can observe member leaving\", async () => {\n\t\tconst { container } = await client.createContainer(schema);\n\t\tconst itemId = await container.attach();\n\n\t\tif (container.connectionState !== ConnectionState.Connected) {\n\t\t\tawait timeoutPromise((resolve) => container.once(\"connected\", () => resolve()), {\n\t\t\t\tdurationMs: connectTimeoutMs,\n\t\t\t\terrorMsg: \"container connect() timeout\",\n\t\t\t});\n\t\t}\n\n\t\t// pass client2 siteUrl and driveId\n\t\tconst client2 = createOdspClient(\n\t\t\tclient2Creds,\n\t\t\tundefined,\n\t\t\tconfigProvider({\n\t\t\t\t\"Fluid.Container.ForceWriteConnection\": true,\n\t\t\t}),\n\t\t);\n\t\tconst { services: servicesGet } = await client2.getContainer(itemId, schema);\n\n\t\t/* This is a workaround for a known bug, we should have one member (self) upon container connection */\n\t\tconst partner = await waitForMember(servicesGet.audience, client2Creds.username);\n\t\tassert.notStrictEqual(partner, undefined, \"We should have partner at this point.\");\n\n\t\tlet members = servicesGet.audience.getMembers();\n\t\tassert.strictEqual(members.size, 2, \"We should have two members at this point.\");\n\n\t\tcontainer.disconnect();\n\n\t\tawait new Promise<void>((resolve) => {\n\t\t\tservicesGet.audience.on(\"memberRemoved\", () => {\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\n\t\tmembers = servicesGet.audience.getMembers();\n\t\tassert.strictEqual(members.size, 1, \"We should have one member left at this point.\");\n\t});\n});\n"]}
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /*!
4
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
5
+ * Licensed under the MIT License.
6
+ */
7
+ const node_assert_1 = require("node:assert");
8
+ const container_definitions_1 = require("@fluidframework/container-definitions");
9
+ const map_1 = require("@fluidframework/map");
10
+ const test_utils_1 = require("@fluidframework/test-utils");
11
+ const container_loader_1 = require("@fluidframework/container-loader");
12
+ const OdspClientFactory_1 = require("./OdspClientFactory");
13
+ const clientCreds = {
14
+ username: process.env.odsp__client__login__username,
15
+ password: process.env.odsp__client__login__password,
16
+ };
17
+ describe("Container create scenarios", () => {
18
+ const connectTimeoutMs = 10000;
19
+ let client;
20
+ let schema;
21
+ beforeEach(() => {
22
+ client = (0, OdspClientFactory_1.createOdspClient)(clientCreds);
23
+ schema = {
24
+ initialObjects: {
25
+ map1: map_1.SharedMap,
26
+ },
27
+ };
28
+ });
29
+ /**
30
+ * Scenario: test when an Odsp Client container is created,
31
+ * it is initially detached.
32
+ *
33
+ * Expected behavior: an error should not be thrown nor should a rejected promise
34
+ * be returned.
35
+ */
36
+ it("Created container is detached", async () => {
37
+ const { container } = await client.createContainer(schema);
38
+ node_assert_1.strict.strictEqual(container.attachState, container_definitions_1.AttachState.Detached, "Container should be detached");
39
+ // Make sure we can attach.
40
+ const itemId = await container.attach();
41
+ node_assert_1.strict.strictEqual(typeof itemId, "string", "Attach did not return a string ID");
42
+ });
43
+ /**
44
+ * Scenario: Test attaching a container.
45
+ *
46
+ * Expected behavior: an error should not be thrown nor should a rejected promise
47
+ * be returned.
48
+ */
49
+ it("can attach a container", async () => {
50
+ const { container } = await client.createContainer(schema);
51
+ const itemId = await container.attach();
52
+ if (container.connectionState !== container_loader_1.ConnectionState.Connected) {
53
+ await (0, test_utils_1.timeoutPromise)((resolve) => container.once("connected", () => resolve()), {
54
+ durationMs: connectTimeoutMs,
55
+ errorMsg: "container connect() timeout",
56
+ });
57
+ }
58
+ node_assert_1.strict.strictEqual(typeof itemId, "string", "Attach did not return a string ID");
59
+ node_assert_1.strict.strictEqual(container.attachState, container_definitions_1.AttachState.Attached, "Container is not attached after attach is called");
60
+ });
61
+ /**
62
+ * Scenario: Test if attaching a container twice fails.
63
+ *
64
+ * Expected behavior: an error should not be thrown nor should a rejected promise
65
+ * be returned.
66
+ */
67
+ it("cannot attach a container twice", async () => {
68
+ const { container } = await client.createContainer(schema);
69
+ const itemId = await container.attach();
70
+ if (container.connectionState !== container_loader_1.ConnectionState.Connected) {
71
+ await (0, test_utils_1.timeoutPromise)((resolve) => container.once("connected", () => resolve()), {
72
+ durationMs: connectTimeoutMs,
73
+ errorMsg: "container connect() timeout",
74
+ });
75
+ }
76
+ node_assert_1.strict.strictEqual(typeof itemId, "string", "Attach did not return a string ID");
77
+ node_assert_1.strict.strictEqual(container.attachState, container_definitions_1.AttachState.Attached, "Container is attached after attach is called");
78
+ await node_assert_1.strict.rejects(container.attach(), () => true, "Container should not attach twice");
79
+ });
80
+ /**
81
+ * Scenario: test if Odsp Client can get an existing container.
82
+ *
83
+ * Expected behavior: an error should not be thrown nor should a rejected promise
84
+ * be returned.
85
+ */
86
+ it("can retrieve existing ODSP container successfully", async () => {
87
+ const { container: newContainer } = await client.createContainer(schema);
88
+ const itemId = await newContainer.attach();
89
+ if (newContainer.connectionState !== container_loader_1.ConnectionState.Connected) {
90
+ await (0, test_utils_1.timeoutPromise)((resolve) => newContainer.once("connected", () => resolve()), {
91
+ durationMs: connectTimeoutMs,
92
+ errorMsg: "container connect() timeout",
93
+ });
94
+ }
95
+ const resources = client.getContainer(itemId, schema);
96
+ await node_assert_1.strict.doesNotReject(resources, () => true, "container cannot be retrieved from ODSP");
97
+ });
98
+ /**
99
+ * Scenario: test if Odsp Client can get a non-exiting container.
100
+ *
101
+ * Expected behavior: an error should be thrown when trying to get a non-existent container.
102
+ */
103
+ it("cannot load improperly created container (cannot load a non-existent container)", async () => {
104
+ const containerAndServicesP = client.getContainer("containerConfig", schema);
105
+ const errorFn = (error) => {
106
+ node_assert_1.strict.notStrictEqual(error.message, undefined, "Odsp Client error is undefined");
107
+ node_assert_1.strict.strict(error.message.startsWith("ODSP fetch error [400]"), `Unexpected error: ${error.message}`);
108
+ return true;
109
+ };
110
+ await node_assert_1.strict.rejects(containerAndServicesP, errorFn, "Odsp Client can load a non-existent container");
111
+ });
112
+ });
113
+ //# sourceMappingURL=containerCreate.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"containerCreate.spec.js","sourceRoot":"","sources":["../../src/test/containerCreate.spec.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\nimport { strict as assert } from \"node:assert\";\n\nimport { OdspClient } from \"@fluid-experimental/odsp-client\";\nimport { AttachState } from \"@fluidframework/container-definitions\";\nimport { ContainerSchema } from \"@fluidframework/fluid-static\";\nimport { SharedMap } from \"@fluidframework/map\";\nimport { timeoutPromise } from \"@fluidframework/test-utils\";\n\nimport { ConnectionState } from \"@fluidframework/container-loader\";\nimport { IOdspLoginCredentials, createOdspClient } from \"./OdspClientFactory\";\n\nconst clientCreds: IOdspLoginCredentials = {\n\tusername: process.env.odsp__client__login__username as string,\n\tpassword: process.env.odsp__client__login__password as string,\n};\n\ndescribe(\"Container create scenarios\", () => {\n\tconst connectTimeoutMs = 10_000;\n\tlet client: OdspClient;\n\tlet schema: ContainerSchema;\n\n\tbeforeEach(() => {\n\t\tclient = createOdspClient(clientCreds);\n\t\tschema = {\n\t\t\tinitialObjects: {\n\t\t\t\tmap1: SharedMap,\n\t\t\t},\n\t\t};\n\t});\n\n\t/**\n\t * Scenario: test when an Odsp Client container is created,\n\t * it is initially detached.\n\t *\n\t * Expected behavior: an error should not be thrown nor should a rejected promise\n\t * be returned.\n\t */\n\tit(\"Created container is detached\", async () => {\n\t\tconst { container } = await client.createContainer(schema);\n\t\tassert.strictEqual(\n\t\t\tcontainer.attachState,\n\t\t\tAttachState.Detached,\n\t\t\t\"Container should be detached\",\n\t\t);\n\n\t\t// Make sure we can attach.\n\t\tconst itemId = await container.attach();\n\t\tassert.strictEqual(typeof itemId, \"string\", \"Attach did not return a string ID\");\n\t});\n\n\t/**\n\t * Scenario: Test attaching a container.\n\t *\n\t * Expected behavior: an error should not be thrown nor should a rejected promise\n\t * be returned.\n\t */\n\tit(\"can attach a container\", async () => {\n\t\tconst { container } = await client.createContainer(schema);\n\t\tconst itemId = await container.attach();\n\n\t\tif (container.connectionState !== ConnectionState.Connected) {\n\t\t\tawait timeoutPromise((resolve) => container.once(\"connected\", () => resolve()), {\n\t\t\t\tdurationMs: connectTimeoutMs,\n\t\t\t\terrorMsg: \"container connect() timeout\",\n\t\t\t});\n\t\t}\n\n\t\tassert.strictEqual(typeof itemId, \"string\", \"Attach did not return a string ID\");\n\t\tassert.strictEqual(\n\t\t\tcontainer.attachState,\n\t\t\tAttachState.Attached,\n\t\t\t\"Container is not attached after attach is called\",\n\t\t);\n\t});\n\n\t/**\n\t * Scenario: Test if attaching a container twice fails.\n\t *\n\t * Expected behavior: an error should not be thrown nor should a rejected promise\n\t * be returned.\n\t */\n\tit(\"cannot attach a container twice\", async () => {\n\t\tconst { container } = await client.createContainer(schema);\n\t\tconst itemId = await container.attach();\n\n\t\tif (container.connectionState !== ConnectionState.Connected) {\n\t\t\tawait timeoutPromise((resolve) => container.once(\"connected\", () => resolve()), {\n\t\t\t\tdurationMs: connectTimeoutMs,\n\t\t\t\terrorMsg: \"container connect() timeout\",\n\t\t\t});\n\t\t}\n\n\t\tassert.strictEqual(typeof itemId, \"string\", \"Attach did not return a string ID\");\n\t\tassert.strictEqual(\n\t\t\tcontainer.attachState,\n\t\t\tAttachState.Attached,\n\t\t\t\"Container is attached after attach is called\",\n\t\t);\n\t\tawait assert.rejects(container.attach(), () => true, \"Container should not attach twice\");\n\t});\n\n\t/**\n\t * Scenario: test if Odsp Client can get an existing container.\n\t *\n\t * Expected behavior: an error should not be thrown nor should a rejected promise\n\t * be returned.\n\t */\n\tit(\"can retrieve existing ODSP container successfully\", async () => {\n\t\tconst { container: newContainer } = await client.createContainer(schema);\n\t\tconst itemId = await newContainer.attach();\n\n\t\tif (newContainer.connectionState !== ConnectionState.Connected) {\n\t\t\tawait timeoutPromise((resolve) => newContainer.once(\"connected\", () => resolve()), {\n\t\t\t\tdurationMs: connectTimeoutMs,\n\t\t\t\terrorMsg: \"container connect() timeout\",\n\t\t\t});\n\t\t}\n\n\t\tconst resources = client.getContainer(itemId, schema);\n\t\tawait assert.doesNotReject(\n\t\t\tresources,\n\t\t\t() => true,\n\t\t\t\"container cannot be retrieved from ODSP\",\n\t\t);\n\t});\n\n\t/**\n\t * Scenario: test if Odsp Client can get a non-exiting container.\n\t *\n\t * Expected behavior: an error should be thrown when trying to get a non-existent container.\n\t */\n\tit(\"cannot load improperly created container (cannot load a non-existent container)\", async () => {\n\t\tconst containerAndServicesP = client.getContainer(\"containerConfig\", schema);\n\n\t\tconst errorFn = (error: Error): boolean => {\n\t\t\tassert.notStrictEqual(error.message, undefined, \"Odsp Client error is undefined\");\n\t\t\tassert.strict(\n\t\t\t\terror.message.startsWith(\"ODSP fetch error [400]\"),\n\t\t\t\t`Unexpected error: ${error.message}`,\n\t\t\t);\n\t\t\treturn true;\n\t\t};\n\n\t\tawait assert.rejects(\n\t\t\tcontainerAndServicesP,\n\t\t\terrorFn,\n\t\t\t\"Odsp Client can load a non-existent container\",\n\t\t);\n\t});\n});\n"]}