@fluidframework/azure-end-to-end-tests 2.21.0 → 2.23.0-323641

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.
@@ -0,0 +1,158 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { strict as assert } from "node:assert";
6
+ import { AzureClient, } from "@fluidframework/azure-client";
7
+ import { AttachState } from "@fluidframework/container-definitions";
8
+ import { ConnectionState } from "@fluidframework/container-loader";
9
+ import { acquirePresenceViaDataObject, ExperimentalPresenceManager,
10
+ // eslint-disable-next-line import/no-internal-modules
11
+ } from "@fluidframework/presence/alpha";
12
+ import { InsecureTokenProvider } from "@fluidframework/test-runtime-utils/internal";
13
+ import { timeoutPromise } from "@fluidframework/test-utils/internal";
14
+ import { createAzureTokenProvider } from "../AzureTokenFactory.js";
15
+ const connectTimeoutMs = 10_000;
16
+ // Identifier given to child process
17
+ const process_id = process.argv[2];
18
+ const useAzure = process.env.FLUID_CLIENT === "azure";
19
+ const tenantId = useAzure
20
+ ? process.env.azure__fluid__relay__service__tenantId
21
+ : "frs-client-tenant";
22
+ const endPoint = process.env.azure__fluid__relay__service__endpoint;
23
+ if (useAzure && endPoint === undefined) {
24
+ throw new Error("Azure Fluid Relay service endpoint is missing");
25
+ }
26
+ /**
27
+ * Get or create a Fluid container with Presence in initialObjects.
28
+ */
29
+ const getOrCreatePresenceContainer = async (id, user, config, scopes) => {
30
+ let container;
31
+ let containerId;
32
+ const connectionProps = useAzure
33
+ ? {
34
+ tenantId,
35
+ tokenProvider: createAzureTokenProvider(user.id ?? "foo", user.name ?? "bar", scopes),
36
+ endpoint: endPoint,
37
+ type: "remote",
38
+ }
39
+ : {
40
+ tokenProvider: new InsecureTokenProvider("fooBar", user, scopes),
41
+ endpoint: "http://localhost:7071",
42
+ type: "local",
43
+ };
44
+ const client = new AzureClient({ connection: connectionProps });
45
+ const schema = {
46
+ initialObjects: {
47
+ presence: ExperimentalPresenceManager,
48
+ },
49
+ };
50
+ let services;
51
+ if (id === undefined) {
52
+ ({ container, services } = await client.createContainer(schema, "2"));
53
+ containerId = await container.attach();
54
+ }
55
+ else {
56
+ containerId = id;
57
+ ({ container, services } = await client.getContainer(containerId, schema, "2"));
58
+ }
59
+ // wait for 'ConnectionState.Connected' so we return with client connected to container
60
+ if (container.connectionState !== ConnectionState.Connected) {
61
+ await timeoutPromise((resolve) => container.once("connected", () => resolve()), {
62
+ durationMs: connectTimeoutMs,
63
+ errorMsg: "container connect() timeout",
64
+ });
65
+ }
66
+ assert.strictEqual(container.attachState, AttachState.Attached, "Container is not attached after attach is called");
67
+ const presence = acquirePresenceViaDataObject(container.initialObjects.presence);
68
+ return {
69
+ client,
70
+ container,
71
+ presence,
72
+ services,
73
+ containerId,
74
+ };
75
+ };
76
+ function createSendFunction() {
77
+ if (process.send) {
78
+ return process.send.bind(process);
79
+ }
80
+ throw new Error("process.send is not defined");
81
+ }
82
+ const send = createSendFunction();
83
+ function isConnected(container) {
84
+ return container !== undefined && container.connectionState === ConnectionState.Connected;
85
+ }
86
+ class MessageHandler {
87
+ async onMessage(msg) {
88
+ switch (msg.command) {
89
+ // Respond to connect command by connecting to Fluid container with the provided user information.
90
+ case "connect": {
91
+ // Check if valid user information has been provided by parent/orchestrator
92
+ if (!msg.user) {
93
+ send({ event: "error", error: `${process_id}: No azure user information given` });
94
+ break;
95
+ }
96
+ // Check if already connected to container
97
+ if (isConnected(this.container)) {
98
+ send({ event: "error", error: `${process_id}: Already connected to container` });
99
+ break;
100
+ }
101
+ const { container, presence, containerId } = await getOrCreatePresenceContainer(msg.containerId, msg.user);
102
+ this.container = container;
103
+ this.presence = presence;
104
+ this.containerId = containerId;
105
+ // Listen for presence events to notify parent/orchestrator when a new attendee joins or leaves the session.
106
+ presence.events.on("attendeeJoined", (attendee) => {
107
+ const m = {
108
+ event: "attendeeJoined",
109
+ sessionId: attendee.sessionId,
110
+ };
111
+ send(m);
112
+ });
113
+ presence.events.on("attendeeDisconnected", (attendee) => {
114
+ const m = {
115
+ event: "attendeeDisconnected",
116
+ sessionId: attendee.sessionId,
117
+ };
118
+ send(m);
119
+ });
120
+ send({ event: "ready", containerId, sessionId: presence.getMyself().sessionId });
121
+ break;
122
+ }
123
+ // Respond to disconnect command by disconnecting self from Fluid container.
124
+ case "disconnectSelf": {
125
+ if (!this.container) {
126
+ send({ event: "error", error: `${process_id} is not connected to container` });
127
+ break;
128
+ }
129
+ if (!this.presence) {
130
+ send({ event: "error", error: `${process_id} is not connected to presence` });
131
+ break;
132
+ }
133
+ this.container.disconnect();
134
+ send({
135
+ event: "disconnectedSelf",
136
+ sessionId: this.presence.getMyself().sessionId,
137
+ });
138
+ break;
139
+ }
140
+ default: {
141
+ console.error(`${process_id}: Unknown command`);
142
+ send({ event: "error", error: `${process_id} Unknown command` });
143
+ break;
144
+ }
145
+ }
146
+ }
147
+ }
148
+ function setupMessageHandler() {
149
+ const messageHandler = new MessageHandler();
150
+ process.on("message", (msg) => {
151
+ messageHandler.onMessage(msg).catch((error) => {
152
+ console.error(`Error in client ${process_id}`, error);
153
+ send({ event: "error", error: `${process_id}: ${error.message}` });
154
+ });
155
+ });
156
+ }
157
+ setupMessageHandler();
158
+ //# sourceMappingURL=childClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"childClient.js","sourceRoot":"","sources":["../../../src/test/multiprocess/childClient.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 */\n\nimport { strict as assert } from \"node:assert\";\n\nimport {\n\tAzureClient,\n\ttype AzureContainerServices,\n\tAzureLocalConnectionConfig,\n\tAzureRemoteConnectionConfig,\n} from \"@fluidframework/azure-client\";\nimport { AttachState } from \"@fluidframework/container-definitions\";\nimport { ConnectionState } from \"@fluidframework/container-loader\";\nimport { ContainerSchema, type IFluidContainer } from \"@fluidframework/fluid-static\";\nimport {\n\tacquirePresenceViaDataObject,\n\tExperimentalPresenceManager,\n\ttype ExperimentalPresenceDO,\n\ttype IPresence,\n\ttype ISessionClient,\n\t// eslint-disable-next-line import/no-internal-modules\n} from \"@fluidframework/presence/alpha\";\nimport { InsecureTokenProvider } from \"@fluidframework/test-runtime-utils/internal\";\nimport { timeoutPromise } from \"@fluidframework/test-utils/internal\";\n\nimport { ScopeType } from \"../AzureClientFactory.js\";\nimport { createAzureTokenProvider } from \"../AzureTokenFactory.js\";\nimport { configProvider } from \"../utils.js\";\n\nimport { MessageFromChild, MessageToChild } from \"./messageTypes.js\";\n\ntype MessageFromParent = MessageToChild;\ntype MessageToParent = Required<MessageFromChild>;\ninterface UserIdAndName {\n\tid: string;\n\tname: string;\n}\n\nconst connectTimeoutMs = 10_000;\n// Identifier given to child process\nconst process_id = process.argv[2];\n\nconst useAzure = process.env.FLUID_CLIENT === \"azure\";\nconst tenantId = useAzure\n\t? (process.env.azure__fluid__relay__service__tenantId as string)\n\t: \"frs-client-tenant\";\nconst endPoint = process.env.azure__fluid__relay__service__endpoint as string;\nif (useAzure && endPoint === undefined) {\n\tthrow new Error(\"Azure Fluid Relay service endpoint is missing\");\n}\n\n/**\n * Get or create a Fluid container with Presence in initialObjects.\n */\nconst getOrCreatePresenceContainer = async (\n\tid: string | undefined,\n\tuser: UserIdAndName,\n\tconfig?: ReturnType<typeof configProvider>,\n\tscopes?: ScopeType[],\n): Promise<{\n\tcontainer: IFluidContainer;\n\tpresence: IPresence;\n\tservices: AzureContainerServices;\n\tclient: AzureClient;\n\tcontainerId: string;\n}> => {\n\tlet container: IFluidContainer;\n\tlet containerId: string;\n\tconst connectionProps: AzureRemoteConnectionConfig | AzureLocalConnectionConfig = useAzure\n\t\t? {\n\t\t\t\ttenantId,\n\t\t\t\ttokenProvider: createAzureTokenProvider(user.id ?? \"foo\", user.name ?? \"bar\", scopes),\n\t\t\t\tendpoint: endPoint,\n\t\t\t\ttype: \"remote\",\n\t\t\t}\n\t\t: {\n\t\t\t\ttokenProvider: new InsecureTokenProvider(\"fooBar\", user, scopes),\n\t\t\t\tendpoint: \"http://localhost:7071\",\n\t\t\t\ttype: \"local\",\n\t\t\t};\n\tconst client = new AzureClient({ connection: connectionProps });\n\tconst schema: ContainerSchema = {\n\t\tinitialObjects: {\n\t\t\tpresence: ExperimentalPresenceManager,\n\t\t},\n\t};\n\tlet services: AzureContainerServices;\n\tif (id === undefined) {\n\t\t({ container, services } = await client.createContainer(schema, \"2\"));\n\t\tcontainerId = await container.attach();\n\t} else {\n\t\tcontainerId = id;\n\t\t({ container, services } = await client.getContainer(containerId, schema, \"2\"));\n\t}\n\t// wait for 'ConnectionState.Connected' so we return with client connected to container\n\tif (container.connectionState !== ConnectionState.Connected) {\n\t\tawait timeoutPromise((resolve) => container.once(\"connected\", () => resolve()), {\n\t\t\tdurationMs: connectTimeoutMs,\n\t\t\terrorMsg: \"container connect() timeout\",\n\t\t});\n\t}\n\tassert.strictEqual(\n\t\tcontainer.attachState,\n\t\tAttachState.Attached,\n\t\t\"Container is not attached after attach is called\",\n\t);\n\n\tconst presence = acquirePresenceViaDataObject(\n\t\tcontainer.initialObjects.presence as ExperimentalPresenceDO,\n\t);\n\treturn {\n\t\tclient,\n\t\tcontainer,\n\t\tpresence,\n\t\tservices,\n\t\tcontainerId,\n\t};\n};\nfunction createSendFunction(): (msg: MessageToParent) => void {\n\tif (process.send) {\n\t\treturn process.send.bind(process);\n\t}\n\tthrow new Error(\"process.send is not defined\");\n}\n\nconst send = createSendFunction();\n\nfunction isConnected(container: IFluidContainer | undefined): boolean {\n\treturn container !== undefined && container.connectionState === ConnectionState.Connected;\n}\n\nclass MessageHandler {\n\tpublic presence: IPresence | undefined;\n\tpublic container: IFluidContainer | undefined;\n\tpublic containerId: string | undefined;\n\n\tpublic async onMessage(msg: MessageFromParent): Promise<void> {\n\t\tswitch (msg.command) {\n\t\t\t// Respond to connect command by connecting to Fluid container with the provided user information.\n\t\t\tcase \"connect\": {\n\t\t\t\t// Check if valid user information has been provided by parent/orchestrator\n\t\t\t\tif (!msg.user) {\n\t\t\t\t\tsend({ event: \"error\", error: `${process_id}: No azure user information given` });\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t// Check if already connected to container\n\t\t\t\tif (isConnected(this.container)) {\n\t\t\t\t\tsend({ event: \"error\", error: `${process_id}: Already connected to container` });\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tconst { container, presence, containerId } = await getOrCreatePresenceContainer(\n\t\t\t\t\tmsg.containerId,\n\t\t\t\t\tmsg.user,\n\t\t\t\t);\n\t\t\t\tthis.container = container;\n\t\t\t\tthis.presence = presence;\n\t\t\t\tthis.containerId = containerId;\n\n\t\t\t\t// Listen for presence events to notify parent/orchestrator when a new attendee joins or leaves the session.\n\t\t\t\tpresence.events.on(\"attendeeJoined\", (attendee: ISessionClient) => {\n\t\t\t\t\tconst m: MessageToParent = {\n\t\t\t\t\t\tevent: \"attendeeJoined\",\n\t\t\t\t\t\tsessionId: attendee.sessionId,\n\t\t\t\t\t};\n\t\t\t\t\tsend(m);\n\t\t\t\t});\n\t\t\t\tpresence.events.on(\"attendeeDisconnected\", (attendee: ISessionClient) => {\n\t\t\t\t\tconst m: MessageToParent = {\n\t\t\t\t\t\tevent: \"attendeeDisconnected\",\n\t\t\t\t\t\tsessionId: attendee.sessionId,\n\t\t\t\t\t};\n\t\t\t\t\tsend(m);\n\t\t\t\t});\n\t\t\t\tsend({ event: \"ready\", containerId, sessionId: presence.getMyself().sessionId });\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Respond to disconnect command by disconnecting self from Fluid container.\n\t\t\tcase \"disconnectSelf\": {\n\t\t\t\tif (!this.container) {\n\t\t\t\t\tsend({ event: \"error\", error: `${process_id} is not connected to container` });\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (!this.presence) {\n\t\t\t\t\tsend({ event: \"error\", error: `${process_id} is not connected to presence` });\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tthis.container.disconnect();\n\t\t\t\tsend({\n\t\t\t\t\tevent: \"disconnectedSelf\",\n\t\t\t\t\tsessionId: this.presence.getMyself().sessionId,\n\t\t\t\t});\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\tconsole.error(`${process_id}: Unknown command`);\n\t\t\t\tsend({ event: \"error\", error: `${process_id} Unknown command` });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction setupMessageHandler(): void {\n\tconst messageHandler = new MessageHandler();\n\tprocess.on(\"message\", (msg: MessageFromParent) => {\n\t\tmessageHandler.onMessage(msg).catch((error: Error) => {\n\t\t\tconsole.error(`Error in client ${process_id}`, error);\n\t\t\tsend({ event: \"error\", error: `${process_id}: ${error.message}` });\n\t\t});\n\t});\n}\n\nsetupMessageHandler();\n"]}
@@ -0,0 +1,6 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=messageTypes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messageTypes.js","sourceRoot":"","sources":["../../../src/test/multiprocess/messageTypes.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { AzureUser } from \"@fluidframework/azure-client/internal\";\n// eslint-disable-next-line import/no-internal-modules\nimport type { ClientSessionId } from \"@fluidframework/presence/alpha\";\n\nexport type MessageToChild = ConnectCommand | DisconnectSelfCommand;\ninterface ConnectCommand {\n\tcommand: \"connect\";\n\tuser: AzureUser;\n\tcontainerId?: string;\n}\n\ninterface DisconnectSelfCommand {\n\tcommand: \"disconnectSelf\";\n}\n\nexport type MessageFromChild =\n\t| AttendeeDisconnectedEvent\n\t| AttendeeJoinedEvent\n\t| ReadyEvent\n\t| DisconnectedSelfEvent\n\t| ErrorEvent;\ninterface AttendeeDisconnectedEvent {\n\tevent: \"attendeeDisconnected\";\n\tsessionId: ClientSessionId;\n}\n\ninterface AttendeeJoinedEvent {\n\tevent: \"attendeeJoined\";\n\tsessionId: ClientSessionId;\n}\n\ninterface ReadyEvent {\n\tevent: \"ready\";\n\tcontainerId: string;\n\tsessionId: ClientSessionId;\n}\n\ninterface DisconnectedSelfEvent {\n\tevent: \"disconnectedSelf\";\n\tsessionId: ClientSessionId;\n}\ninterface ErrorEvent {\n\tevent: \"error\";\n\terror: string;\n}\n"]}
@@ -0,0 +1,146 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { strict as assert } from "node:assert";
6
+ import { fork } from "node:child_process";
7
+ import { timeoutPromise } from "@fluidframework/test-utils/internal";
8
+ /**
9
+ * This test suite is a prototype for a multi-process end to end test for Fluid using the new Presence API on AzureClient.
10
+ * In the future we hope to expand and generalize this pattern to broadly test more Fluid features.
11
+ * Currently our E2E tests are limited to running multiple clients on a single process which does not effectively
12
+ * simulate real-world production scenarios where clients are usually running on different machines.
13
+ *
14
+ * The pattern demonstrated in this test suite is as follows:
15
+ *
16
+ * This main test file acts as the 'Orchestrator'. The orchestrator's job includes:
17
+ * - Fork child processes to simulate multiple Fluid clients
18
+ * - Send command messages to child clients to perform specific Fluid actions.
19
+ * - Receive response messages from child clients to verify expected behavior.
20
+ * - Clean up child processes after each test.
21
+ *
22
+ * The child processes are located in the `childClient.ts` file. Each child process simulates a Fluid client.
23
+ *
24
+ * The child client's job includes:
25
+ * - Create/Get + connect to Fluid container.
26
+ * - Listen for command messages from the orchestrator.
27
+ * - Perform the requested action.
28
+ * - Send response messages including any relevant data back to the orchestrator to verify expected behavior.
29
+ *
30
+ * This particular test suite tests the following E2E functionality for Presence:
31
+ * - Announce 'attendeeJoined' when remote client joins session.
32
+ * - Announce 'attendeeDisconnected' when remote client disconnects.
33
+ */
34
+ describe(`Presence with AzureClient`, () => {
35
+ const numClients = 5; // Set the total number of Fluid clients to create
36
+ assert(numClients > 1, "Must have at least two clients");
37
+ let children = [];
38
+ // This promise is used to capture all errors that occur in the child processes.
39
+ // It will never resolve successfully, it is only used to reject on child process error.
40
+ let childErrorPromise;
41
+ // Timeout duration used when waiting for response messages from child processes.
42
+ const durationMs = 10_000;
43
+ const afterCleanUp = [];
44
+ async function connectChildProcesses(childProcesses) {
45
+ let containerIdPromise;
46
+ let containerCreatorSessionId;
47
+ for (const [index, child] of childProcesses.entries()) {
48
+ const user = { id: `test-user-id-${index}`, name: `test-user-name-${index}` };
49
+ const message = { command: "connect", user };
50
+ if (containerIdPromise === undefined) {
51
+ // Create a promise that resolves with the containerId from the created container.
52
+ containerIdPromise = timeoutPromise((resolve, reject) => {
53
+ child.once("message", (msg) => {
54
+ if (msg.event === "ready" && msg.containerId) {
55
+ containerCreatorSessionId = msg.sessionId;
56
+ resolve(msg.containerId);
57
+ }
58
+ else {
59
+ reject(new Error(`Non-ready message from child0: ${JSON.stringify(msg)}`));
60
+ }
61
+ });
62
+ }, {
63
+ durationMs,
64
+ errorMsg: "did not receive 'ready' from child process",
65
+ });
66
+ }
67
+ else {
68
+ // For subsequent children, wait for containerId from the promise only when needed.
69
+ message.containerId = await containerIdPromise;
70
+ }
71
+ child.send(message);
72
+ }
73
+ return containerCreatorSessionId;
74
+ }
75
+ beforeEach("setup", async () => {
76
+ // Collect all child process error promises into this array
77
+ const childErrorPromises = [];
78
+ // Fork child processes
79
+ for (let i = 0; i < numClients; i++) {
80
+ const child = fork("./lib/test/multiprocess/childClient.js", [
81
+ `child${i}` /* identifier passed to child process */,
82
+ ]);
83
+ const errorPromise = new Promise((_, reject) => {
84
+ child.on("error", (error) => {
85
+ reject(new Error(`Child${i} process errored: ${error.message}`));
86
+ });
87
+ });
88
+ childErrorPromises.push(errorPromise);
89
+ children.push(child);
90
+ // Register cleanup for the child process listeners.
91
+ afterCleanUp.push(() => child.removeAllListeners());
92
+ }
93
+ // This race will be used to reject any of the following tests on any child process errors
94
+ childErrorPromise = Promise.race(childErrorPromises);
95
+ });
96
+ // After each test, kill each child process and call any cleanup functions that were registered
97
+ afterEach(async () => {
98
+ for (const child of children) {
99
+ child.kill();
100
+ }
101
+ children = [];
102
+ for (const cleanUp of afterCleanUp) {
103
+ cleanUp();
104
+ }
105
+ afterCleanUp.length = 0;
106
+ });
107
+ it("announces 'attendeeJoined' when remote client joins session and 'attendeeDisconnected' when remote client disconnects", async () => {
108
+ // Setup
109
+ const attendeeJoinedPromise = timeoutPromise((resolve) => {
110
+ let attendeesJoinedEvents = 0;
111
+ children[0].on("message", (msg) => {
112
+ if (msg.event === "attendeeJoined") {
113
+ attendeesJoinedEvents++;
114
+ if (attendeesJoinedEvents === numClients - 1) {
115
+ resolve();
116
+ }
117
+ }
118
+ });
119
+ }, {
120
+ durationMs,
121
+ errorMsg: "did not receive all 'attendeeJoined' events",
122
+ });
123
+ // Act - connect all child processes
124
+ const creatorSessionId = await connectChildProcesses(children);
125
+ // Verify - wait for all 'attendeeJoined' events
126
+ await Promise.race([attendeeJoinedPromise, childErrorPromise]);
127
+ // Setup
128
+ const waitForDisconnected = children
129
+ .filter((_, index) => index !== 0)
130
+ .map(async (child, index) => timeoutPromise((resolve) => {
131
+ child.on("message", (msg) => {
132
+ if (msg.event === "attendeeDisconnected" && msg.sessionId === creatorSessionId) {
133
+ resolve();
134
+ }
135
+ });
136
+ }, {
137
+ durationMs,
138
+ errorMsg: `Attendee[${index}] Disconnected Timeout`,
139
+ }));
140
+ // Act - disconnect first child process
141
+ children[0].send({ command: "disconnectSelf" });
142
+ // Verify - wait for all 'attendeeDisconnected' events
143
+ await Promise.race([Promise.all(waitForDisconnected), childErrorPromise]);
144
+ });
145
+ });
146
+ //# sourceMappingURL=presenceTest.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presenceTest.spec.js","sourceRoot":"","sources":["../../../src/test/multiprocess/presenceTest.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 */\n\nimport { strict as assert } from \"node:assert\";\nimport { fork, ChildProcess } from \"node:child_process\";\n\nimport { timeoutPromise } from \"@fluidframework/test-utils/internal\";\n\nimport type { MessageFromChild, MessageToChild } from \"./messageTypes.js\";\n\n/**\n * This test suite is a prototype for a multi-process end to end test for Fluid using the new Presence API on AzureClient.\n * In the future we hope to expand and generalize this pattern to broadly test more Fluid features.\n * Currently our E2E tests are limited to running multiple clients on a single process which does not effectively\n * simulate real-world production scenarios where clients are usually running on different machines.\n *\n * The pattern demonstrated in this test suite is as follows:\n *\n * This main test file acts as the 'Orchestrator'. The orchestrator's job includes:\n * - Fork child processes to simulate multiple Fluid clients\n * - Send command messages to child clients to perform specific Fluid actions.\n * - Receive response messages from child clients to verify expected behavior.\n * - Clean up child processes after each test.\n *\n * The child processes are located in the `childClient.ts` file. Each child process simulates a Fluid client.\n *\n * The child client's job includes:\n * - Create/Get + connect to Fluid container.\n * - Listen for command messages from the orchestrator.\n * - Perform the requested action.\n * - Send response messages including any relevant data back to the orchestrator to verify expected behavior.\n *\n * This particular test suite tests the following E2E functionality for Presence:\n * - Announce 'attendeeJoined' when remote client joins session.\n * - Announce 'attendeeDisconnected' when remote client disconnects.\n */\ndescribe(`Presence with AzureClient`, () => {\n\tconst numClients = 5; // Set the total number of Fluid clients to create\n\tassert(numClients > 1, \"Must have at least two clients\");\n\tlet children: ChildProcess[] = [];\n\t// This promise is used to capture all errors that occur in the child processes.\n\t// It will never resolve successfully, it is only used to reject on child process error.\n\tlet childErrorPromise: Promise<void>;\n\t// Timeout duration used when waiting for response messages from child processes.\n\tconst durationMs = 10_000;\n\n\tconst afterCleanUp: (() => void)[] = [];\n\n\tasync function connectChildProcesses(\n\t\tchildProcesses: ChildProcess[],\n\t): Promise<string | undefined> {\n\t\tlet containerIdPromise: Promise<string> | undefined;\n\t\tlet containerCreatorSessionId: string | undefined;\n\t\tfor (const [index, child] of childProcesses.entries()) {\n\t\t\tconst user = { id: `test-user-id-${index}`, name: `test-user-name-${index}` };\n\t\t\tconst message: MessageToChild = { command: \"connect\", user };\n\n\t\t\tif (containerIdPromise === undefined) {\n\t\t\t\t// Create a promise that resolves with the containerId from the created container.\n\t\t\t\tcontainerIdPromise = timeoutPromise<string>(\n\t\t\t\t\t(resolve, reject) => {\n\t\t\t\t\t\tchild.once(\"message\", (msg: MessageFromChild) => {\n\t\t\t\t\t\t\tif (msg.event === \"ready\" && msg.containerId) {\n\t\t\t\t\t\t\t\tcontainerCreatorSessionId = msg.sessionId;\n\t\t\t\t\t\t\t\tresolve(msg.containerId);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\treject(new Error(`Non-ready message from child0: ${JSON.stringify(msg)}`));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tdurationMs,\n\t\t\t\t\t\terrorMsg: \"did not receive 'ready' from child process\",\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\t// For subsequent children, wait for containerId from the promise only when needed.\n\t\t\t\tmessage.containerId = await containerIdPromise;\n\t\t\t}\n\n\t\t\tchild.send(message);\n\t\t}\n\t\treturn containerCreatorSessionId;\n\t}\n\n\tbeforeEach(\"setup\", async () => {\n\t\t// Collect all child process error promises into this array\n\t\tconst childErrorPromises: Promise<void>[] = [];\n\t\t// Fork child processes\n\t\tfor (let i = 0; i < numClients; i++) {\n\t\t\tconst child = fork(\"./lib/test/multiprocess/childClient.js\", [\n\t\t\t\t`child${i}` /* identifier passed to child process */,\n\t\t\t]);\n\t\t\tconst errorPromise = new Promise<void>((_, reject) => {\n\t\t\t\tchild.on(\"error\", (error) => {\n\t\t\t\t\treject(new Error(`Child${i} process errored: ${error.message}`));\n\t\t\t\t});\n\t\t\t});\n\t\t\tchildErrorPromises.push(errorPromise);\n\t\t\tchildren.push(child);\n\t\t\t// Register cleanup for the child process listeners.\n\t\t\tafterCleanUp.push(() => child.removeAllListeners());\n\t\t}\n\t\t// This race will be used to reject any of the following tests on any child process errors\n\t\tchildErrorPromise = Promise.race(childErrorPromises);\n\t});\n\n\t// After each test, kill each child process and call any cleanup functions that were registered\n\tafterEach(async () => {\n\t\tfor (const child of children) {\n\t\t\tchild.kill();\n\t\t}\n\t\tchildren = [];\n\t\tfor (const cleanUp of afterCleanUp) {\n\t\t\tcleanUp();\n\t\t}\n\t\tafterCleanUp.length = 0;\n\t});\n\n\tit(\"announces 'attendeeJoined' when remote client joins session and 'attendeeDisconnected' when remote client disconnects\", async () => {\n\t\t// Setup\n\t\tconst attendeeJoinedPromise = timeoutPromise(\n\t\t\t(resolve) => {\n\t\t\t\tlet attendeesJoinedEvents = 0;\n\t\t\t\tchildren[0].on(\"message\", (msg: MessageFromChild) => {\n\t\t\t\t\tif (msg.event === \"attendeeJoined\") {\n\t\t\t\t\t\tattendeesJoinedEvents++;\n\t\t\t\t\t\tif (attendeesJoinedEvents === numClients - 1) {\n\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t},\n\t\t\t{\n\t\t\t\tdurationMs,\n\t\t\t\terrorMsg: \"did not receive all 'attendeeJoined' events\",\n\t\t\t},\n\t\t);\n\n\t\t// Act - connect all child processes\n\t\tconst creatorSessionId = await connectChildProcesses(children);\n\n\t\t// Verify - wait for all 'attendeeJoined' events\n\t\tawait Promise.race([attendeeJoinedPromise, childErrorPromise]);\n\n\t\t// Setup\n\t\tconst waitForDisconnected = children\n\t\t\t.filter((_, index) => index !== 0)\n\t\t\t.map(async (child, index) =>\n\t\t\t\ttimeoutPromise(\n\t\t\t\t\t(resolve) => {\n\t\t\t\t\t\tchild.on(\"message\", (msg: MessageFromChild) => {\n\t\t\t\t\t\t\tif (msg.event === \"attendeeDisconnected\" && msg.sessionId === creatorSessionId) {\n\t\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tdurationMs,\n\t\t\t\t\t\terrorMsg: `Attendee[${index}] Disconnected Timeout`,\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t);\n\n\t\t// Act - disconnect first child process\n\t\tchildren[0].send({ command: \"disconnectSelf\" });\n\n\t\t// Verify - wait for all 'attendeeDisconnected' events\n\t\tawait Promise.race([Promise.all(waitForDisconnected), childErrorPromise]);\n\t});\n});\n"]}
@@ -10,7 +10,7 @@ import { createAzureClient, createContainerFromPayload, getContainerIdFromPayloa
10
10
  import { SignalerTestDataObject } from "./TestDataObject.js";
11
11
  import * as ephemeralSummaryTrees from "./ephemeralSummaryTrees.js";
12
12
  import { configProvider, getTestMatrix } from "./utils.js";
13
- async function createSignalListenerPromise(signaler, signalType, expectedPayload, name = "Signal", timeoutMs = 10000) {
13
+ async function createSignalListenerPromise(signaler, signalType, expectedPayload, name = "Signal", timeoutMs = 10_000) {
14
14
  return timeoutPromise((resolve, reject) => {
15
15
  signaler.onSignal(signalType, (clientId, local, receivedPayload) => {
16
16
  try {
@@ -27,7 +27,7 @@ const testMatrix = getTestMatrix();
27
27
  for (const testOpts of testMatrix) {
28
28
  describe(`Fluid Signals (${testOpts.variant})`, () => {
29
29
  const connectedContainers = [];
30
- const connectTimeoutMs = 10000;
30
+ const connectTimeoutMs = 10_000;
31
31
  const isEphemeral = testOpts.options.isEphemeral;
32
32
  const user1 = {
33
33
  id: "test-user-id-1",
@@ -1 +1 @@
1
- {"version":3,"file":"signals.spec.js","sourceRoot":"","sources":["../../src/test/signals.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 */\n\nimport { strict as assert } from \"node:assert\";\n\nimport { AzureClient, type AzureContainerServices } from \"@fluidframework/azure-client\";\nimport { AttachState } from \"@fluidframework/container-definitions\";\nimport { ConnectionState } from \"@fluidframework/container-loader\";\nimport { type ContainerSchema, type IFluidContainer } from \"@fluidframework/fluid-static\";\nimport { timeoutPromise } from \"@fluidframework/test-utils/internal\";\nimport type { AxiosResponse } from \"axios\";\n\nimport {\n\tcreateAzureClient,\n\tcreateContainerFromPayload,\n\tgetContainerIdFromPayloadResponse,\n\tScopeType,\n} from \"./AzureClientFactory.js\";\nimport { SignalerTestDataObject } from \"./TestDataObject.js\";\nimport * as ephemeralSummaryTrees from \"./ephemeralSummaryTrees.js\";\nimport { configProvider, getTestMatrix } from \"./utils.js\";\n\ninterface UserIdAndName {\n\treadonly id: string;\n\treadonly name: string;\n}\n\nasync function createSignalListenerPromise<T>(\n\tsignaler: SignalerTestDataObject,\n\tsignalType: string,\n\texpectedPayload: T,\n\tname: string = \"Signal\",\n\ttimeoutMs: number = 10_000,\n): Promise<T> {\n\treturn timeoutPromise(\n\t\t(resolve, reject) => {\n\t\t\tsignaler.onSignal<T>(signalType, (clientId, local, receivedPayload) => {\n\t\t\t\ttry {\n\t\t\t\t\tassert.deepStrictEqual(\n\t\t\t\t\t\treceivedPayload,\n\t\t\t\t\t\texpectedPayload,\n\t\t\t\t\t\t\"Received payload does not match expected payload\",\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn reject(error);\n\t\t\t\t}\n\t\t\t\tresolve(receivedPayload);\n\t\t\t});\n\t\t},\n\t\t{ durationMs: timeoutMs, errorMsg: `${name}: listener timeout` },\n\t);\n}\n\nconst testMatrix = getTestMatrix();\nfor (const testOpts of testMatrix) {\n\tdescribe(`Fluid Signals (${testOpts.variant})`, () => {\n\t\tconst connectedContainers: IFluidContainer[] = [];\n\t\tconst connectTimeoutMs = 10_000;\n\t\tconst isEphemeral: boolean = testOpts.options.isEphemeral;\n\t\tconst user1 = {\n\t\t\tid: \"test-user-id-1\",\n\t\t\tname: \"test-user-name-2\",\n\t\t} as const satisfies UserIdAndName;\n\t\tconst user2 = {\n\t\t\tid: \"test-user-id-1\",\n\t\t\tname: \"test-user-name-2\",\n\t\t} as const satisfies UserIdAndName;\n\t\tconst user3 = {\n\t\t\tid: \"test-user-id-1\",\n\t\t\tname: \"test-user-name-2\",\n\t\t} as const satisfies UserIdAndName;\n\n\t\tafterEach(async () => {\n\t\t\tfor (const container of connectedContainers) {\n\t\t\t\tcontainer.disconnect();\n\t\t\t\tcontainer.dispose();\n\t\t\t}\n\t\t\tconnectedContainers.splice(0, connectedContainers.length);\n\t\t});\n\n\t\tconst getOrCreateSignalerContainer = async (\n\t\t\tid: string | undefined,\n\t\t\tuser: UserIdAndName,\n\t\t\tconfig?: ReturnType<typeof configProvider>,\n\t\t\tscopes?: ScopeType[],\n\t\t): Promise<{\n\t\t\tcontainer: IFluidContainer;\n\t\t\tsignaler: SignalerTestDataObject;\n\t\t\tservices: AzureContainerServices;\n\t\t\tclient: AzureClient;\n\t\t\tcontainerId: string;\n\t\t}> => {\n\t\t\tconst client = createAzureClient(user.id, user.name, undefined, config, scopes);\n\t\t\tconst schema: ContainerSchema = {\n\t\t\t\tinitialObjects: {\n\t\t\t\t\tsignaler: SignalerTestDataObject,\n\t\t\t\t},\n\t\t\t};\n\t\t\tlet container: IFluidContainer;\n\t\t\tlet services: AzureContainerServices;\n\t\t\tlet containerId: string;\n\t\t\tif (id === undefined) {\n\t\t\t\tif (isEphemeral) {\n\t\t\t\t\tconst containerResponse: AxiosResponse | undefined =\n\t\t\t\t\t\tawait createContainerFromPayload(\n\t\t\t\t\t\t\tephemeralSummaryTrees.sendAndRecieveSignals,\n\t\t\t\t\t\t\t\"test-user-id-1\",\n\t\t\t\t\t\t\t\"test-user-name-1\",\n\t\t\t\t\t\t);\n\t\t\t\t\tcontainerId = getContainerIdFromPayloadResponse(containerResponse);\n\t\t\t\t\t({ container, services } = await client.getContainer(containerId, schema, \"2\"));\n\t\t\t\t} else {\n\t\t\t\t\t({ container, services } = await client.createContainer(schema, \"2\"));\n\t\t\t\t\tcontainerId = await container.attach();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcontainerId = id;\n\t\t\t\t({ container, services } = await client.getContainer(containerId, schema, \"2\"));\n\t\t\t}\n\n\t\t\tif (container.connectionState !== ConnectionState.Connected) {\n\t\t\t\tawait timeoutPromise((resolve) => container.once(\"connected\", () => resolve()), {\n\t\t\t\t\tdurationMs: connectTimeoutMs,\n\t\t\t\t\terrorMsg: \"container connect() timeout\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tconnectedContainers.push(container);\n\n\t\t\tassert.strictEqual(typeof containerId, \"string\", \"Attach did not return a string ID\");\n\t\t\tassert.strictEqual(\n\t\t\t\tcontainer.attachState,\n\t\t\t\tAttachState.Attached,\n\t\t\t\t\"Container is not attached after attach is called\",\n\t\t\t);\n\n\t\t\tconst signaler = container.initialObjects.signaler as SignalerTestDataObject;\n\t\t\treturn {\n\t\t\t\tclient,\n\t\t\t\tcontainer,\n\t\t\t\tsignaler,\n\t\t\t\tservices,\n\t\t\t\tcontainerId,\n\t\t\t};\n\t\t};\n\n\t\t/**\n\t\t * Scenario: Client sends a signal and connected clients receive it.\n\t\t *\n\t\t * Expected behavior: While 2 clients are connected to a container,\n\t\t * a signal sent by 1 client should be recieved by both clients.\n\t\t */\n\t\tit(\"can send and receive signals\", async () => {\n\t\t\tconst { signaler, containerId } = await getOrCreateSignalerContainer(undefined, user1);\n\t\t\tconst { signaler: signaler2 } = await getOrCreateSignalerContainer(\n\t\t\t\tcontainerId,\n\t\t\t\tuser2,\n\t\t\t\tconfigProvider({\n\t\t\t\t\t\"Fluid.Container.ForceWriteConnection\": true,\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\tconst signalName = \"test-signal\";\n\t\t\tconst signalPayload = { test: \"payload\" };\n\n\t\t\tconst listenerPromises = [\n\t\t\t\tcreateSignalListenerPromise(\n\t\t\t\t\tsignaler2,\n\t\t\t\t\tsignalName,\n\t\t\t\t\tsignalPayload,\n\t\t\t\t\t\"Write client listening for write client signal\",\n\t\t\t\t),\n\t\t\t\tcreateSignalListenerPromise(\n\t\t\t\t\tsignaler,\n\t\t\t\t\tsignalName,\n\t\t\t\t\tsignalPayload,\n\t\t\t\t\t\"Write client listening for its own signal\",\n\t\t\t\t),\n\t\t\t];\n\n\t\t\tsignaler.submitSignal(signalName, signalPayload);\n\n\t\t\tawait assert.doesNotReject(\n\t\t\t\tPromise.all(listenerPromises),\n\t\t\t\t\"Listening clients should receive signals.\",\n\t\t\t);\n\t\t});\n\n\t\t/**\n\t\t * Scenario: Read and Write clients send signals and connected clients receive them.\n\t\t *\n\t\t * Expected behavior: While 2 clients are connected (1 writer, 2 readers) to a container,\n\t\t * a signal sent by any 1 client should be recieved by all 3 clients, regardless of read/write permissions.\n\t\t */\n\t\tit(\"can send and receive read-only client signals\", async function () {\n\t\t\tconst { signaler: writeSignaler, containerId } = await getOrCreateSignalerContainer(\n\t\t\t\tundefined,\n\t\t\t\tuser1,\n\t\t\t);\n\t\t\tconst { signaler: readSignaler } = await getOrCreateSignalerContainer(\n\t\t\t\tcontainerId,\n\t\t\t\tuser2,\n\t\t\t\tundefined,\n\t\t\t\t[ScopeType.DocRead],\n\t\t\t);\n\t\t\tconst { signaler: readSignaler2 } = await getOrCreateSignalerContainer(\n\t\t\t\tcontainerId,\n\t\t\t\tuser3,\n\t\t\t\tundefined,\n\t\t\t\t[ScopeType.DocRead],\n\t\t\t);\n\n\t\t\tconst signalName = \"test-signal\";\n\n\t\t\tconst signalPayload1 = { test: \"payload\" };\n\t\t\tconst listenerPromises1 = [\n\t\t\t\tcreateSignalListenerPromise(\n\t\t\t\t\twriteSignaler,\n\t\t\t\t\tsignalName,\n\t\t\t\t\tsignalPayload1,\n\t\t\t\t\t\"Write client listening for read client signal\",\n\t\t\t\t),\n\t\t\t\tcreateSignalListenerPromise(\n\t\t\t\t\treadSignaler2,\n\t\t\t\t\tsignalName,\n\t\t\t\t\tsignalPayload1,\n\t\t\t\t\t\"Read client 2 listening for read client signal\",\n\t\t\t\t),\n\t\t\t\tcreateSignalListenerPromise(\n\t\t\t\t\treadSignaler,\n\t\t\t\t\tsignalName,\n\t\t\t\t\tsignalPayload1,\n\t\t\t\t\t\"Read client listening for its own signal\",\n\t\t\t\t),\n\t\t\t];\n\t\t\treadSignaler.submitSignal(signalName, signalPayload1);\n\t\t\tawait assert.doesNotReject(\n\t\t\t\tPromise.all(listenerPromises1),\n\t\t\t\t\"Listening clients should receive signals from read clients.\",\n\t\t\t);\n\n\t\t\tconst signalPayload2 = { test: \"payload2\" };\n\t\t\tconst listenerPromises2 = [\n\t\t\t\tcreateSignalListenerPromise(\n\t\t\t\t\treadSignaler,\n\t\t\t\t\tsignalName,\n\t\t\t\t\tsignalPayload2,\n\t\t\t\t\t\"Read client listening for write client signal\",\n\t\t\t\t),\n\t\t\t\tcreateSignalListenerPromise(\n\t\t\t\t\treadSignaler2,\n\t\t\t\t\tsignalName,\n\t\t\t\t\tsignalPayload2,\n\t\t\t\t\t\"Read client 2 listening for write client signal\",\n\t\t\t\t),\n\t\t\t\tcreateSignalListenerPromise(\n\t\t\t\t\twriteSignaler,\n\t\t\t\t\tsignalName,\n\t\t\t\t\tsignalPayload2,\n\t\t\t\t\t\"Write client listening for its own signal\",\n\t\t\t\t),\n\t\t\t];\n\t\t\twriteSignaler.submitSignal(signalName, signalPayload2);\n\t\t\tawait assert.doesNotReject(\n\t\t\t\tPromise.all(listenerPromises2),\n\t\t\t\t\"Listening clients should receive signals from write clients.\",\n\t\t\t);\n\t\t});\n\t});\n}\n"]}
1
+ {"version":3,"file":"signals.spec.js","sourceRoot":"","sources":["../../src/test/signals.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 */\n\nimport { strict as assert } from \"node:assert\";\n\nimport { AzureClient, type AzureContainerServices } from \"@fluidframework/azure-client\";\nimport { AttachState } from \"@fluidframework/container-definitions\";\nimport { ConnectionState } from \"@fluidframework/container-loader\";\nimport { type ContainerSchema, type IFluidContainer } from \"@fluidframework/fluid-static\";\nimport { timeoutPromise } from \"@fluidframework/test-utils/internal\";\nimport type { AxiosResponse } from \"axios\";\n\nimport {\n\tcreateAzureClient,\n\tcreateContainerFromPayload,\n\tgetContainerIdFromPayloadResponse,\n\tScopeType,\n} from \"./AzureClientFactory.js\";\nimport { SignalerTestDataObject } from \"./TestDataObject.js\";\nimport * as ephemeralSummaryTrees from \"./ephemeralSummaryTrees.js\";\nimport { configProvider, getTestMatrix } from \"./utils.js\";\n\ninterface UserIdAndName {\n\treadonly id: string;\n\treadonly name: string;\n}\n\nasync function createSignalListenerPromise<T>(\n\tsignaler: SignalerTestDataObject,\n\tsignalType: string,\n\texpectedPayload: T,\n\tname: string = \"Signal\",\n\ttimeoutMs: number = 10_000,\n): Promise<T> {\n\treturn timeoutPromise(\n\t\t(resolve, reject) => {\n\t\t\tsignaler.onSignal<T>(signalType, (clientId, local, receivedPayload) => {\n\t\t\t\ttry {\n\t\t\t\t\tassert.deepStrictEqual(\n\t\t\t\t\t\treceivedPayload,\n\t\t\t\t\t\texpectedPayload,\n\t\t\t\t\t\t\"Received payload does not match expected payload\",\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn reject(error);\n\t\t\t\t}\n\t\t\t\tresolve(receivedPayload);\n\t\t\t});\n\t\t},\n\t\t{ durationMs: timeoutMs, errorMsg: `${name}: listener timeout` },\n\t);\n}\n\nconst testMatrix = getTestMatrix();\nfor (const testOpts of testMatrix) {\n\tdescribe(`Fluid Signals (${testOpts.variant})`, () => {\n\t\tconst connectedContainers: IFluidContainer[] = [];\n\t\tconst connectTimeoutMs = 10_000;\n\t\tconst isEphemeral: boolean = testOpts.options.isEphemeral;\n\t\tconst user1 = {\n\t\t\tid: \"test-user-id-1\",\n\t\t\tname: \"test-user-name-2\",\n\t\t} as const satisfies UserIdAndName;\n\t\tconst user2 = {\n\t\t\tid: \"test-user-id-1\",\n\t\t\tname: \"test-user-name-2\",\n\t\t} as const satisfies UserIdAndName;\n\t\tconst user3 = {\n\t\t\tid: \"test-user-id-1\",\n\t\t\tname: \"test-user-name-2\",\n\t\t} as const satisfies UserIdAndName;\n\n\t\tafterEach(async () => {\n\t\t\tfor (const container of connectedContainers) {\n\t\t\t\tcontainer.disconnect();\n\t\t\t\tcontainer.dispose();\n\t\t\t}\n\t\t\tconnectedContainers.splice(0, connectedContainers.length);\n\t\t});\n\n\t\tconst getOrCreateSignalerContainer = async (\n\t\t\tid: string | undefined,\n\t\t\tuser: UserIdAndName,\n\t\t\tconfig?: ReturnType<typeof configProvider>,\n\t\t\tscopes?: ScopeType[],\n\t\t): Promise<{\n\t\t\tcontainer: IFluidContainer;\n\t\t\tsignaler: SignalerTestDataObject;\n\t\t\tservices: AzureContainerServices;\n\t\t\tclient: AzureClient;\n\t\t\tcontainerId: string;\n\t\t}> => {\n\t\t\tconst client = createAzureClient(user.id, user.name, undefined, config, scopes);\n\t\t\tconst schema: ContainerSchema = {\n\t\t\t\tinitialObjects: {\n\t\t\t\t\tsignaler: SignalerTestDataObject,\n\t\t\t\t},\n\t\t\t};\n\t\t\tlet container: IFluidContainer;\n\t\t\tlet services: AzureContainerServices;\n\t\t\tlet containerId: string;\n\t\t\tif (id === undefined) {\n\t\t\t\tif (isEphemeral) {\n\t\t\t\t\tconst containerResponse: AxiosResponse | undefined =\n\t\t\t\t\t\tawait createContainerFromPayload(\n\t\t\t\t\t\t\tephemeralSummaryTrees.sendAndRecieveSignals,\n\t\t\t\t\t\t\t\"test-user-id-1\",\n\t\t\t\t\t\t\t\"test-user-name-1\",\n\t\t\t\t\t\t);\n\t\t\t\t\tcontainerId = getContainerIdFromPayloadResponse(containerResponse);\n\t\t\t\t\t({ container, services } = await client.getContainer(containerId, schema, \"2\"));\n\t\t\t\t} else {\n\t\t\t\t\t({ container, services } = await client.createContainer(schema, \"2\"));\n\t\t\t\t\tcontainerId = await container.attach();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcontainerId = id;\n\t\t\t\t({ container, services } = await client.getContainer(containerId, schema, \"2\"));\n\t\t\t}\n\n\t\t\tif (container.connectionState !== ConnectionState.Connected) {\n\t\t\t\tawait timeoutPromise((resolve) => container.once(\"connected\", () => resolve()), {\n\t\t\t\t\tdurationMs: connectTimeoutMs,\n\t\t\t\t\terrorMsg: \"container connect() timeout\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tconnectedContainers.push(container);\n\n\t\t\tassert.strictEqual(typeof containerId, \"string\", \"Attach did not return a string ID\");\n\t\t\tassert.strictEqual(\n\t\t\t\tcontainer.attachState,\n\t\t\t\tAttachState.Attached,\n\t\t\t\t\"Container is not attached after attach is called\",\n\t\t\t);\n\n\t\t\tconst signaler = container.initialObjects.signaler as SignalerTestDataObject;\n\t\t\treturn {\n\t\t\t\tclient,\n\t\t\t\tcontainer,\n\t\t\t\tsignaler,\n\t\t\t\tservices,\n\t\t\t\tcontainerId,\n\t\t\t};\n\t\t};\n\n\t\t/**\n\t\t * Scenario: Client sends a signal and connected clients receive it.\n\t\t *\n\t\t * Expected behavior: While 2 clients are connected to a container,\n\t\t * a signal sent by 1 client should be recieved by both clients.\n\t\t */\n\t\tit(\"can send and receive signals\", async () => {\n\t\t\tconst { signaler, containerId } = await getOrCreateSignalerContainer(undefined, user1);\n\t\t\tconst { signaler: signaler2 } = await getOrCreateSignalerContainer(\n\t\t\t\tcontainerId,\n\t\t\t\tuser2,\n\t\t\t\tconfigProvider({\n\t\t\t\t\t\"Fluid.Container.ForceWriteConnection\": true,\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\tconst signalName = \"test-signal\";\n\t\t\tconst signalPayload = { test: \"payload\" };\n\n\t\t\tconst listenerPromises = [\n\t\t\t\tcreateSignalListenerPromise(\n\t\t\t\t\tsignaler2,\n\t\t\t\t\tsignalName,\n\t\t\t\t\tsignalPayload,\n\t\t\t\t\t\"Write client listening for write client signal\",\n\t\t\t\t),\n\t\t\t\tcreateSignalListenerPromise(\n\t\t\t\t\tsignaler,\n\t\t\t\t\tsignalName,\n\t\t\t\t\tsignalPayload,\n\t\t\t\t\t\"Write client listening for its own signal\",\n\t\t\t\t),\n\t\t\t];\n\n\t\t\tsignaler.submitSignal(signalName, signalPayload);\n\n\t\t\tawait assert.doesNotReject(\n\t\t\t\tPromise.all(listenerPromises),\n\t\t\t\t\"Listening clients should receive signals.\",\n\t\t\t);\n\t\t});\n\n\t\t/**\n\t\t * Scenario: Read and Write clients send signals and connected clients receive them.\n\t\t *\n\t\t * Expected behavior: While 2 clients are connected (1 writer, 2 readers) to a container,\n\t\t * a signal sent by any 1 client should be recieved by all 3 clients, regardless of read/write permissions.\n\t\t */\n\t\tit(\"can send and receive read-only client signals\", async function () {\n\t\t\tconst { signaler: writeSignaler, containerId } = await getOrCreateSignalerContainer(\n\t\t\t\tundefined,\n\t\t\t\tuser1,\n\t\t\t);\n\t\t\tconst { signaler: readSignaler } = await getOrCreateSignalerContainer(\n\t\t\t\tcontainerId,\n\t\t\t\tuser2,\n\t\t\t\tundefined,\n\t\t\t\t[ScopeType.DocRead],\n\t\t\t);\n\t\t\tconst { signaler: readSignaler2 } = await getOrCreateSignalerContainer(\n\t\t\t\tcontainerId,\n\t\t\t\tuser3,\n\t\t\t\tundefined,\n\t\t\t\t[ScopeType.DocRead],\n\t\t\t);\n\n\t\t\tconst signalName = \"test-signal\";\n\n\t\t\tconst signalPayload1 = { test: \"payload\" };\n\t\t\tconst listenerPromises1 = [\n\t\t\t\tcreateSignalListenerPromise(\n\t\t\t\t\twriteSignaler,\n\t\t\t\t\tsignalName,\n\t\t\t\t\tsignalPayload1,\n\t\t\t\t\t\"Write client listening for read client signal\",\n\t\t\t\t),\n\t\t\t\tcreateSignalListenerPromise(\n\t\t\t\t\treadSignaler2,\n\t\t\t\t\tsignalName,\n\t\t\t\t\tsignalPayload1,\n\t\t\t\t\t\"Read client 2 listening for read client signal\",\n\t\t\t\t),\n\t\t\t\tcreateSignalListenerPromise(\n\t\t\t\t\treadSignaler,\n\t\t\t\t\tsignalName,\n\t\t\t\t\tsignalPayload1,\n\t\t\t\t\t\"Read client listening for its own signal\",\n\t\t\t\t),\n\t\t\t];\n\t\t\treadSignaler.submitSignal(signalName, signalPayload1);\n\t\t\tawait assert.doesNotReject(\n\t\t\t\tPromise.all(listenerPromises1),\n\t\t\t\t\"Listening clients should receive signals from read clients.\",\n\t\t\t);\n\n\t\t\tconst signalPayload2 = { test: \"payload2\" };\n\t\t\tconst listenerPromises2 = [\n\t\t\t\tcreateSignalListenerPromise(\n\t\t\t\t\treadSignaler,\n\t\t\t\t\tsignalName,\n\t\t\t\t\tsignalPayload2,\n\t\t\t\t\t\"Read client listening for write client signal\",\n\t\t\t\t),\n\t\t\t\tcreateSignalListenerPromise(\n\t\t\t\t\treadSignaler2,\n\t\t\t\t\tsignalName,\n\t\t\t\t\tsignalPayload2,\n\t\t\t\t\t\"Read client 2 listening for write client signal\",\n\t\t\t\t),\n\t\t\t\tcreateSignalListenerPromise(\n\t\t\t\t\twriteSignaler,\n\t\t\t\t\tsignalName,\n\t\t\t\t\tsignalPayload2,\n\t\t\t\t\t\"Write client listening for its own signal\",\n\t\t\t\t),\n\t\t\t];\n\t\t\twriteSignaler.submitSignal(signalName, signalPayload2);\n\t\t\tawait assert.doesNotReject(\n\t\t\t\tPromise.all(listenerPromises2),\n\t\t\t\t\"Listening clients should receive signals from write clients.\",\n\t\t\t);\n\t\t});\n\t});\n}\n"]}
@@ -39,7 +39,7 @@ const treeConfiguration = new TreeViewConfiguration(
39
39
  const testMatrix = getTestMatrix();
40
40
  for (const testOpts of testMatrix) {
41
41
  describe(`SharedTree with AzureClient (${testOpts.variant})`, () => {
42
- const connectTimeoutMs = 10000;
42
+ const connectTimeoutMs = 10_000;
43
43
  const isEphemeral = testOpts.options.isEphemeral;
44
44
  let client;
45
45
  const schema = {
@@ -1 +1 @@
1
- {"version":3,"file":"tree.spec.js","sourceRoot":"","sources":["../../src/test/tree.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 */\n\nimport { strict as assert } from \"node:assert\";\n\nimport { AzureClient } from \"@fluidframework/azure-client\";\nimport { ConnectionState } from \"@fluidframework/container-loader\";\nimport { ContainerSchema, type IFluidContainer } from \"@fluidframework/fluid-static\";\nimport { timeoutPromise } from \"@fluidframework/test-utils/internal\";\nimport { TreeViewConfiguration, SchemaFactory, type TreeView } from \"@fluidframework/tree\";\nimport {\n\tasTreeViewAlpha,\n\tSharedTree,\n\tTree,\n\tTreeStatus,\n\ttype Revertible,\n} from \"@fluidframework/tree/internal\";\nimport type { AxiosResponse } from \"axios\";\n\nimport {\n\tcreateAzureClient,\n\tcreateContainerFromPayload,\n\tgetContainerIdFromPayloadResponse,\n} from \"./AzureClientFactory.js\";\nimport * as ephemeralSummaryTrees from \"./ephemeralSummaryTrees.js\";\nimport { getTestMatrix } from \"./utils.js\";\n\nconst sf = new SchemaFactory(\"d302b84c-75f6-4ecd-9663-524f467013e3\");\n\n/**\n * Define a class that is an array of strings\n * This class is used to create an array in the SharedTree\n */\nclass StringArray extends sf.array(\"StringArray\", sf.string) {\n\t/**\n\t * Remove the first item in the list if the list is not empty\n\t */\n\tpublic removeFirst(): void {\n\t\tif (this.length > 0) this.removeAt(0);\n\t}\n\n\t/**\n\t * Add an item to the beginning of the list\n\t */\n\tpublic insertNew(str: string): void {\n\t\tthis.insertAtStart(str);\n\t}\n}\n\n/**\n * This object is passed into the SharedTree via the schematize method.\n */\nconst treeConfiguration = new TreeViewConfiguration(\n\t// Specify the root type - StringArray.\n\t{ schema: StringArray },\n);\n\nconst testMatrix = getTestMatrix();\nfor (const testOpts of testMatrix) {\n\tdescribe(`SharedTree with AzureClient (${testOpts.variant})`, () => {\n\t\tconst connectTimeoutMs = 10_000;\n\t\tconst isEphemeral: boolean = testOpts.options.isEphemeral;\n\t\tlet client: AzureClient;\n\t\tconst schema = {\n\t\t\tinitialObjects: {\n\t\t\t\ttree1: SharedTree,\n\t\t\t},\n\t\t} satisfies ContainerSchema;\n\n\t\tbeforeEach(\"createAzureClient\", () => {\n\t\t\tclient = createAzureClient();\n\t\t});\n\n\t\tasync function waitForConnection(container: IFluidContainer): Promise<void> {\n\t\t\tif (container.connectionState !== ConnectionState.Connected) {\n\t\t\t\tawait timeoutPromise((resolve) => container.once(\"connected\", () => resolve()), {\n\t\t\t\t\tdurationMs: connectTimeoutMs,\n\t\t\t\t\terrorMsg: \"container1 connect() timeout\",\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Either creates a new azure client with a SharedTree, or loads an azure client from the existing summary tree (the \"ephemeral\" case).\n\t\t */\n\t\tasync function createOrLoad(\n\t\t\tsummaryTree?: (typeof ephemeralSummaryTrees)[keyof typeof ephemeralSummaryTrees],\n\t\t): Promise<{ containerId: string; treeData: TreeView<typeof StringArray> }> {\n\t\t\tlet containerId: string;\n\t\t\tlet treeData: TreeView<typeof StringArray>;\n\n\t\t\tif (summaryTree === undefined) {\n\t\t\t\tconst { container } = await client.createContainer(schema, \"2\");\n\t\t\t\ttreeData = container.initialObjects.tree1.viewWith(treeConfiguration);\n\t\t\t\ttreeData.initialize(new StringArray([]));\n\t\t\t\tcontainerId = await container.attach();\n\t\t\t\tawait waitForConnection(container);\n\t\t\t} else {\n\t\t\t\tconst containerResponse: AxiosResponse | undefined = await createContainerFromPayload(\n\t\t\t\t\tsummaryTree,\n\t\t\t\t\t\"test-user-id-1\",\n\t\t\t\t\t\"test-user-name-1\",\n\t\t\t\t);\n\n\t\t\t\tcontainerId = getContainerIdFromPayloadResponse(containerResponse);\n\t\t\t\tconst { container } = await client.getContainer(containerId, schema, \"2\");\n\t\t\t\ttreeData = container.initialObjects.tree1.viewWith(treeConfiguration);\n\t\t\t\tawait waitForConnection(container);\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tcontainerId,\n\t\t\t\ttreeData,\n\t\t\t};\n\t\t}\n\n\t\tit(\"can create/load a container with SharedTree and do basic ops\", async () => {\n\t\t\tconst { treeData } = await createOrLoad(\n\t\t\t\tisEphemeral ? ephemeralSummaryTrees.createContainerWithSharedTree : undefined,\n\t\t\t);\n\n\t\t\ttreeData.root.insertNew(\"test string 1\");\n\t\t\tassert.strictEqual(treeData.root.length, 1);\n\t\t\tassert.strictEqual(treeData.root.at(0), \"test string 1\");\n\n\t\t\ttreeData.root.insertNew(\"test string 2\");\n\t\t\tassert.strictEqual(treeData.root.length, 2);\n\t\t\tassert.strictEqual(treeData.root.at(0), \"test string 2\");\n\t\t\tassert.strictEqual(treeData.root.at(1), \"test string 1\");\n\n\t\t\ttreeData.root.removeFirst();\n\t\t\tassert.strictEqual(treeData.root.length, 1);\n\t\t\tassert.strictEqual(treeData.root.at(0), \"test string 1\");\n\t\t});\n\n\t\tit(\"can create/load a container with SharedTree collaborate with basic ops\", async () => {\n\t\t\tconst { containerId, treeData } = await createOrLoad(\n\t\t\t\tisEphemeral ? ephemeralSummaryTrees.createLoadContainerWithSharedTree : undefined,\n\t\t\t);\n\n\t\t\ttreeData.root.insertNew(\"test string 1\");\n\n\t\t\tconst resources = client.getContainer(containerId, schema, \"2\");\n\t\t\tawait assert.doesNotReject(\n\t\t\t\tresources,\n\t\t\t\t() => true,\n\t\t\t\t\"container cannot be retrieved from Azure Fluid Relay\",\n\t\t\t);\n\t\t\tconst { container: container2 } = await resources;\n\t\t\tassert.deepStrictEqual(\n\t\t\t\tObject.keys(container2.initialObjects),\n\t\t\t\tObject.keys(schema.initialObjects),\n\t\t\t);\n\n\t\t\tif (container2.connectionState !== ConnectionState.Connected) {\n\t\t\t\tawait timeoutPromise((resolve) => container2.once(\"connected\", () => resolve()), {\n\t\t\t\t\tdurationMs: connectTimeoutMs,\n\t\t\t\t\terrorMsg: \"container2 connect() timeout\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst treeData2 = container2.initialObjects.tree1.viewWith(treeConfiguration);\n\t\t\tassert.strictEqual(treeData2.root.length, 1);\n\t\t\tassert.strictEqual(treeData2.root.at(0), \"test string 1\");\n\t\t});\n\n\t\tif (!isEphemeral) {\n\t\t\t{\n\t\t\t\tclass Nicknames extends sf.array(\"Nicknames\", sf.string) {}\n\t\t\t\tclass UserData extends sf.map(\"UserData\", [sf.string, sf.number, sf.boolean]) {}\n\t\t\t\tclass User extends sf.object(\"User\", {\n\t\t\t\t\tname: sf.string,\n\t\t\t\t\tnicknames: Nicknames,\n\t\t\t\t\tdata: UserData,\n\t\t\t\t}) {}\n\n\t\t\t\tit(\"can read and edit data\", async () => {\n\t\t\t\t\tconst { container } = await client.createContainer(schema, \"2\");\n\t\t\t\t\tawait container.attach();\n\t\t\t\t\tconst view = container.initialObjects.tree1.viewWith(\n\t\t\t\t\t\tnew TreeViewConfiguration({ schema: User, enableSchemaValidation: true }),\n\t\t\t\t\t);\n\n\t\t\t\t\tconst tags: [string, string | number | boolean][] = [\n\t\t\t\t\t\t[\"Age\", 32],\n\t\t\t\t\t\t[\"Favorite Snack\", \"Potato Chips\"],\n\t\t\t\t\t\t[\"Awake\", true],\n\t\t\t\t\t];\n\n\t\t\t\t\tview.initialize(\n\t\t\t\t\t\tnew User({\n\t\t\t\t\t\t\tname: \"Pardes\",\n\t\t\t\t\t\t\tnicknames: [\"Alex\", \"Duder\"],\n\t\t\t\t\t\t\tdata: new Map(tags),\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\n\t\t\t\t\tconst user = view.root;\n\t\t\t\t\t// Read data\n\t\t\t\t\tassert.equal(user.name, \"Pardes\");\n\t\t\t\t\tassert.deepEqual([...user.nicknames], [\"Alex\", \"Duder\"]);\n\t\t\t\t\tassert.equal(user.data.get(\"Age\"), 32);\n\t\t\t\t\tassert.equal(user.data.get(\"Favorite Snack\"), \"Potato Chips\");\n\t\t\t\t\tassert.equal(user.data.get(\"Awake\"), true);\n\t\t\t\t\t// Mutate data\n\t\t\t\t\tuser.name = \"Pardesio\";\n\t\t\t\t\tuser.nicknames.insertAt(1, \"Alexp\");\n\t\t\t\t\tuser.data.set(\"Awake\", false);\n\t\t\t\t\tuser.data.set(\"Favorite Sport\", \"Ultimate Frisbee\");\n\t\t\t\t\t// Read mutated data\n\t\t\t\t\tassert.equal(user.name, \"Pardesio\");\n\t\t\t\t\tassert.deepEqual([...user.nicknames], [\"Alex\", \"Alexp\", \"Duder\"]);\n\t\t\t\t\tassert.equal(user.data.get(\"Age\"), 32);\n\t\t\t\t\tassert.equal(user.data.get(\"Awake\"), false);\n\t\t\t\t\tassert.equal(user.data.get(\"Favorite Sport\"), \"Ultimate Frisbee\");\n\t\t\t\t});\n\n\t\t\t\tit(\"can handle undo/redo and transactions\", async () => {\n\t\t\t\t\tconst { container } = await client.createContainer(schema, \"2\");\n\t\t\t\t\tawait container.attach();\n\t\t\t\t\tconst view = asTreeViewAlpha(\n\t\t\t\t\t\tcontainer.initialObjects.tree1.viewWith(\n\t\t\t\t\t\t\tnew TreeViewConfiguration({ schema: User, enableSchemaValidation: true }),\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\n\t\t\t\t\tview.initialize(\n\t\t\t\t\t\tnew User({\n\t\t\t\t\t\t\tname: \"Shrek\",\n\t\t\t\t\t\t\tnicknames: [\"Ogre\"],\n\t\t\t\t\t\t\tdata: new Map([[\"Color\", \"a5bf2e\"]]),\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\n\t\t\t\t\tconst user = view.root;\n\t\t\t\t\t// Capture the Revertible so that changes can be undone\n\t\t\t\t\tlet revertible: Revertible | undefined;\n\t\t\t\t\tview.events.on(\"changed\", (_, getRevertible) => {\n\t\t\t\t\t\tassert(getRevertible !== undefined);\n\t\t\t\t\t\trevertible = getRevertible();\n\t\t\t\t\t});\n\t\t\t\t\t// Change a field, then revert the change\n\t\t\t\t\tuser.name = \"Donkey\";\n\t\t\t\t\tassert.equal(user.name, \"Donkey\");\n\t\t\t\t\tassert(revertible !== undefined);\n\t\t\t\t\trevertible.revert();\n\t\t\t\t\tassert.equal(user.name, \"Shrek\");\n\t\t\t\t\t// Run a transaction which changes multiple fields, then revert it\n\t\t\t\t\tTree.runTransaction(user, (u) => {\n\t\t\t\t\t\tu.name = \"Donkey\";\n\t\t\t\t\t\tu.nicknames.removeRange();\n\t\t\t\t\t\tu.data.set(\"Color\", \"8e8170\");\n\t\t\t\t\t});\n\n\t\t\t\t\tassert.equal(user.name, \"Donkey\");\n\t\t\t\t\tassert.equal(user.nicknames.length, 0);\n\t\t\t\t\tassert.equal(user.data.get(\"Color\"), \"8e8170\");\n\t\t\t\t\trevertible.revert();\n\t\t\t\t\tassert.equal(user.name, \"Shrek\");\n\t\t\t\t\tassert.equal(user.nicknames[0], \"Ogre\");\n\t\t\t\t\tassert.equal(user.data.get(\"Color\"), \"a5bf2e\");\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tit(\"can use identifiers and the static Tree APIs\", async () => {\n\t\t\t\tclass Widget extends sf.object(\"Widget\", { id: sf.identifier }) {}\n\n\t\t\t\tconst { container } = await client.createContainer(schema, \"2\");\n\t\t\t\tawait container.attach();\n\t\t\t\tconst view = container.initialObjects.tree1.viewWith(\n\t\t\t\t\tnew TreeViewConfiguration({\n\t\t\t\t\t\tschema: sf.array(Widget),\n\t\t\t\t\t\tenableSchemaValidation: true,\n\t\t\t\t\t}),\n\t\t\t\t);\n\n\t\t\t\tview.initialize([new Widget({}), new Widget({ id: \"fidget\" })]);\n\n\t\t\t\tconst widget = view.root.at(0);\n\t\t\t\tassert(widget !== undefined);\n\t\t\t\tconst fidget = view.root.at(-1);\n\t\t\t\tassert(fidget !== undefined);\n\t\t\t\t// Test various Tree.* APIs and ensure they are working\n\t\t\t\tassert.equal(Tree.contains(view.root, widget), true);\n\t\t\t\tassert.equal(Tree.contains(fidget, widget), false);\n\t\t\t\tassert.equal(Tree.is(fidget, Widget), true);\n\t\t\t\tassert.equal(Tree.is(view.root, Widget), false);\n\t\t\t\tassert.equal(Tree.key(widget), 0);\n\t\t\t\tassert.equal(Tree.key(fidget), 1);\n\t\t\t\tassert.equal(Tree.parent(widget), view.root);\n\t\t\t\tassert.equal(Tree.schema(fidget), Widget);\n\t\t\t\tassert.equal(typeof Tree.shortId(widget), \"number\");\n\t\t\t\tassert.equal(Tree.shortId(fidget), \"fidget\");\n\t\t\t\tassert.equal(Tree.status(widget), TreeStatus.InDocument);\n\t\t\t});\n\n\t\t\tit(\"can listen to events on a recursive tree\", async () => {\n\t\t\t\tclass Doll extends sf.objectRecursive(\"Matryoshka\", {\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/explicit-function-return-type\n\t\t\t\t\tnested: sf.optionalRecursive([() => Doll]),\n\t\t\t\t}) {}\n\n\t\t\t\tconst { container } = await client.createContainer(schema, \"2\");\n\t\t\t\tawait container.attach();\n\t\t\t\tconst view = container.initialObjects.tree1.viewWith(\n\t\t\t\t\tnew TreeViewConfiguration({ schema: Doll, enableSchemaValidation: true }),\n\t\t\t\t);\n\n\t\t\t\t// These nodes in the initial tree are unhydrated...\n\t\t\t\tconst depth1 = new Doll({ nested: new Doll({}) });\n\t\t\t\tconst depth0 = new Doll({ nested: depth1 });\n\t\t\t\tview.initialize(depth0);\n\t\t\t\t// ...and confirmed to be the same nodes we get when we read the tree after initialization\n\t\t\t\tassert.equal(view.root, depth0);\n\t\t\t\tassert.equal(view.root.nested, depth1);\n\t\t\t\t// Record a list of the node events fired\n\t\t\t\tconst eventLog: string[] = [];\n\t\t\t\tTree.on(depth0, \"nodeChanged\", () => {\n\t\t\t\t\teventLog.push(\"depth0.nested changed\");\n\t\t\t\t});\n\t\t\t\tTree.on(depth1, \"nodeChanged\", () => {\n\t\t\t\t\teventLog.push(\"depth1.nested changed\");\n\t\t\t\t});\n\t\t\t\t// This event registration happens on the unhydrated (not yet inserted) node\n\t\t\t\tconst newDepth2 = new Doll({});\n\t\t\t\tTree.on(newDepth2, \"nodeChanged\", () => {\n\t\t\t\t\teventLog.push(\"depth2.nested changed\");\n\t\t\t\t});\n\t\t\t\t// Fire the events by doing mutations\n\t\t\t\tdepth1.nested = newDepth2; // \"depth1.nested changed\"\n\t\t\t\tassert.equal(depth1.nested, newDepth2);\n\t\t\t\tdepth1.nested.nested = new Doll({}); // \"depth2.nested changed\"\n\t\t\t\tdepth0.nested = new Doll({}); // \"depth0.nested changed\"\n\t\t\t\t// Ensure the events fired in the expected order\n\t\t\t\tassert.deepEqual(eventLog, [\n\t\t\t\t\t\"depth1.nested changed\",\n\t\t\t\t\t\"depth2.nested changed\",\n\t\t\t\t\t\"depth0.nested changed\",\n\t\t\t\t]);\n\t\t\t});\n\t\t}\n\t});\n}\n"]}
1
+ {"version":3,"file":"tree.spec.js","sourceRoot":"","sources":["../../src/test/tree.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 */\n\nimport { strict as assert } from \"node:assert\";\n\nimport { AzureClient } from \"@fluidframework/azure-client\";\nimport { ConnectionState } from \"@fluidframework/container-loader\";\nimport { ContainerSchema, type IFluidContainer } from \"@fluidframework/fluid-static\";\nimport { timeoutPromise } from \"@fluidframework/test-utils/internal\";\nimport { TreeViewConfiguration, SchemaFactory, type TreeView } from \"@fluidframework/tree\";\nimport {\n\tasTreeViewAlpha,\n\tSharedTree,\n\tTree,\n\tTreeStatus,\n\ttype Revertible,\n} from \"@fluidframework/tree/internal\";\nimport type { AxiosResponse } from \"axios\";\n\nimport {\n\tcreateAzureClient,\n\tcreateContainerFromPayload,\n\tgetContainerIdFromPayloadResponse,\n} from \"./AzureClientFactory.js\";\nimport * as ephemeralSummaryTrees from \"./ephemeralSummaryTrees.js\";\nimport { getTestMatrix } from \"./utils.js\";\n\nconst sf = new SchemaFactory(\"d302b84c-75f6-4ecd-9663-524f467013e3\");\n\n/**\n * Define a class that is an array of strings\n * This class is used to create an array in the SharedTree\n */\nclass StringArray extends sf.array(\"StringArray\", sf.string) {\n\t/**\n\t * Remove the first item in the list if the list is not empty\n\t */\n\tpublic removeFirst(): void {\n\t\tif (this.length > 0) this.removeAt(0);\n\t}\n\n\t/**\n\t * Add an item to the beginning of the list\n\t */\n\tpublic insertNew(str: string): void {\n\t\tthis.insertAtStart(str);\n\t}\n}\n\n/**\n * This object is passed into the SharedTree via the schematize method.\n */\nconst treeConfiguration = new TreeViewConfiguration(\n\t// Specify the root type - StringArray.\n\t{ schema: StringArray },\n);\n\nconst testMatrix = getTestMatrix();\nfor (const testOpts of testMatrix) {\n\tdescribe(`SharedTree with AzureClient (${testOpts.variant})`, () => {\n\t\tconst connectTimeoutMs = 10_000;\n\t\tconst isEphemeral: boolean = testOpts.options.isEphemeral;\n\t\tlet client: AzureClient;\n\t\tconst schema = {\n\t\t\tinitialObjects: {\n\t\t\t\ttree1: SharedTree,\n\t\t\t},\n\t\t} satisfies ContainerSchema;\n\n\t\tbeforeEach(\"createAzureClient\", () => {\n\t\t\tclient = createAzureClient();\n\t\t});\n\n\t\tasync function waitForConnection(container: IFluidContainer): Promise<void> {\n\t\t\tif (container.connectionState !== ConnectionState.Connected) {\n\t\t\t\tawait timeoutPromise((resolve) => container.once(\"connected\", () => resolve()), {\n\t\t\t\t\tdurationMs: connectTimeoutMs,\n\t\t\t\t\terrorMsg: \"container1 connect() timeout\",\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Either creates a new azure client with a SharedTree, or loads an azure client from the existing summary tree (the \"ephemeral\" case).\n\t\t */\n\t\tasync function createOrLoad(\n\t\t\tsummaryTree?: (typeof ephemeralSummaryTrees)[keyof typeof ephemeralSummaryTrees],\n\t\t): Promise<{ containerId: string; treeData: TreeView<typeof StringArray> }> {\n\t\t\tlet containerId: string;\n\t\t\tlet treeData: TreeView<typeof StringArray>;\n\n\t\t\tif (summaryTree === undefined) {\n\t\t\t\tconst { container } = await client.createContainer(schema, \"2\");\n\t\t\t\ttreeData = container.initialObjects.tree1.viewWith(treeConfiguration);\n\t\t\t\ttreeData.initialize(new StringArray([]));\n\t\t\t\tcontainerId = await container.attach();\n\t\t\t\tawait waitForConnection(container);\n\t\t\t} else {\n\t\t\t\tconst containerResponse: AxiosResponse | undefined = await createContainerFromPayload(\n\t\t\t\t\tsummaryTree,\n\t\t\t\t\t\"test-user-id-1\",\n\t\t\t\t\t\"test-user-name-1\",\n\t\t\t\t);\n\n\t\t\t\tcontainerId = getContainerIdFromPayloadResponse(containerResponse);\n\t\t\t\tconst { container } = await client.getContainer(containerId, schema, \"2\");\n\t\t\t\ttreeData = container.initialObjects.tree1.viewWith(treeConfiguration);\n\t\t\t\tawait waitForConnection(container);\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tcontainerId,\n\t\t\t\ttreeData,\n\t\t\t};\n\t\t}\n\n\t\tit(\"can create/load a container with SharedTree and do basic ops\", async () => {\n\t\t\tconst { treeData } = await createOrLoad(\n\t\t\t\tisEphemeral ? ephemeralSummaryTrees.createContainerWithSharedTree : undefined,\n\t\t\t);\n\n\t\t\ttreeData.root.insertNew(\"test string 1\");\n\t\t\tassert.strictEqual(treeData.root.length, 1);\n\t\t\tassert.strictEqual(treeData.root.at(0), \"test string 1\");\n\n\t\t\ttreeData.root.insertNew(\"test string 2\");\n\t\t\tassert.strictEqual(treeData.root.length, 2);\n\t\t\tassert.strictEqual(treeData.root.at(0), \"test string 2\");\n\t\t\tassert.strictEqual(treeData.root.at(1), \"test string 1\");\n\n\t\t\ttreeData.root.removeFirst();\n\t\t\tassert.strictEqual(treeData.root.length, 1);\n\t\t\tassert.strictEqual(treeData.root.at(0), \"test string 1\");\n\t\t});\n\n\t\tit(\"can create/load a container with SharedTree collaborate with basic ops\", async () => {\n\t\t\tconst { containerId, treeData } = await createOrLoad(\n\t\t\t\tisEphemeral ? ephemeralSummaryTrees.createLoadContainerWithSharedTree : undefined,\n\t\t\t);\n\n\t\t\ttreeData.root.insertNew(\"test string 1\");\n\n\t\t\tconst resources = client.getContainer(containerId, schema, \"2\");\n\t\t\tawait assert.doesNotReject(\n\t\t\t\tresources,\n\t\t\t\t() => true,\n\t\t\t\t\"container cannot be retrieved from Azure Fluid Relay\",\n\t\t\t);\n\t\t\tconst { container: container2 } = await resources;\n\t\t\tassert.deepStrictEqual(\n\t\t\t\tObject.keys(container2.initialObjects),\n\t\t\t\tObject.keys(schema.initialObjects),\n\t\t\t);\n\n\t\t\tif (container2.connectionState !== ConnectionState.Connected) {\n\t\t\t\tawait timeoutPromise((resolve) => container2.once(\"connected\", () => resolve()), {\n\t\t\t\t\tdurationMs: connectTimeoutMs,\n\t\t\t\t\terrorMsg: \"container2 connect() timeout\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst treeData2 = container2.initialObjects.tree1.viewWith(treeConfiguration);\n\t\t\tassert.strictEqual(treeData2.root.length, 1);\n\t\t\tassert.strictEqual(treeData2.root.at(0), \"test string 1\");\n\t\t});\n\n\t\tif (!isEphemeral) {\n\t\t\t{\n\t\t\t\tclass Nicknames extends sf.array(\"Nicknames\", sf.string) {}\n\t\t\t\tclass UserData extends sf.map(\"UserData\", [sf.string, sf.number, sf.boolean]) {}\n\t\t\t\tclass User extends sf.object(\"User\", {\n\t\t\t\t\tname: sf.string,\n\t\t\t\t\tnicknames: Nicknames,\n\t\t\t\t\tdata: UserData,\n\t\t\t\t}) {}\n\n\t\t\t\tit(\"can read and edit data\", async () => {\n\t\t\t\t\tconst { container } = await client.createContainer(schema, \"2\");\n\t\t\t\t\tawait container.attach();\n\t\t\t\t\tconst view = container.initialObjects.tree1.viewWith(\n\t\t\t\t\t\tnew TreeViewConfiguration({ schema: User, enableSchemaValidation: true }),\n\t\t\t\t\t);\n\n\t\t\t\t\tconst tags: [string, string | number | boolean][] = [\n\t\t\t\t\t\t[\"Age\", 32],\n\t\t\t\t\t\t[\"Favorite Snack\", \"Potato Chips\"],\n\t\t\t\t\t\t[\"Awake\", true],\n\t\t\t\t\t];\n\n\t\t\t\t\tview.initialize(\n\t\t\t\t\t\tnew User({\n\t\t\t\t\t\t\tname: \"Pardes\",\n\t\t\t\t\t\t\tnicknames: [\"Alex\", \"Duder\"],\n\t\t\t\t\t\t\tdata: new Map(tags),\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\n\t\t\t\t\tconst user = view.root;\n\t\t\t\t\t// Read data\n\t\t\t\t\tassert.equal(user.name, \"Pardes\");\n\t\t\t\t\tassert.deepEqual([...user.nicknames], [\"Alex\", \"Duder\"]);\n\t\t\t\t\tassert.equal(user.data.get(\"Age\"), 32);\n\t\t\t\t\tassert.equal(user.data.get(\"Favorite Snack\"), \"Potato Chips\");\n\t\t\t\t\tassert.equal(user.data.get(\"Awake\"), true);\n\t\t\t\t\t// Mutate data\n\t\t\t\t\tuser.name = \"Pardesio\";\n\t\t\t\t\tuser.nicknames.insertAt(1, \"Alexp\");\n\t\t\t\t\tuser.data.set(\"Awake\", false);\n\t\t\t\t\tuser.data.set(\"Favorite Sport\", \"Ultimate Frisbee\");\n\t\t\t\t\t// Read mutated data\n\t\t\t\t\tassert.equal(user.name, \"Pardesio\");\n\t\t\t\t\tassert.deepEqual([...user.nicknames], [\"Alex\", \"Alexp\", \"Duder\"]);\n\t\t\t\t\tassert.equal(user.data.get(\"Age\"), 32);\n\t\t\t\t\tassert.equal(user.data.get(\"Awake\"), false);\n\t\t\t\t\tassert.equal(user.data.get(\"Favorite Sport\"), \"Ultimate Frisbee\");\n\t\t\t\t});\n\n\t\t\t\tit(\"can handle undo/redo and transactions\", async () => {\n\t\t\t\t\tconst { container } = await client.createContainer(schema, \"2\");\n\t\t\t\t\tawait container.attach();\n\t\t\t\t\tconst view = asTreeViewAlpha(\n\t\t\t\t\t\tcontainer.initialObjects.tree1.viewWith(\n\t\t\t\t\t\t\tnew TreeViewConfiguration({ schema: User, enableSchemaValidation: true }),\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\n\t\t\t\t\tview.initialize(\n\t\t\t\t\t\tnew User({\n\t\t\t\t\t\t\tname: \"Shrek\",\n\t\t\t\t\t\t\tnicknames: [\"Ogre\"],\n\t\t\t\t\t\t\tdata: new Map([[\"Color\", \"a5bf2e\"]]),\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\n\t\t\t\t\tconst user = view.root;\n\t\t\t\t\t// Capture the Revertible so that changes can be undone\n\t\t\t\t\tlet revertible: Revertible | undefined;\n\t\t\t\t\tview.events.on(\"changed\", (_, getRevertible) => {\n\t\t\t\t\t\tassert(getRevertible !== undefined);\n\t\t\t\t\t\trevertible = getRevertible();\n\t\t\t\t\t});\n\t\t\t\t\t// Change a field, then revert the change\n\t\t\t\t\tuser.name = \"Donkey\";\n\t\t\t\t\tassert.equal(user.name, \"Donkey\");\n\t\t\t\t\tassert(revertible !== undefined);\n\t\t\t\t\trevertible.revert();\n\t\t\t\t\tassert.equal(user.name, \"Shrek\");\n\t\t\t\t\t// Run a transaction which changes multiple fields, then revert it\n\t\t\t\t\tTree.runTransaction(user, (u) => {\n\t\t\t\t\t\tu.name = \"Donkey\";\n\t\t\t\t\t\tu.nicknames.removeRange();\n\t\t\t\t\t\tu.data.set(\"Color\", \"8e8170\");\n\t\t\t\t\t});\n\n\t\t\t\t\tassert.equal(user.name, \"Donkey\");\n\t\t\t\t\tassert.equal(user.nicknames.length, 0);\n\t\t\t\t\tassert.equal(user.data.get(\"Color\"), \"8e8170\");\n\t\t\t\t\trevertible.revert();\n\t\t\t\t\tassert.equal(user.name, \"Shrek\");\n\t\t\t\t\tassert.equal(user.nicknames[0], \"Ogre\");\n\t\t\t\t\tassert.equal(user.data.get(\"Color\"), \"a5bf2e\");\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tit(\"can use identifiers and the static Tree APIs\", async () => {\n\t\t\t\tclass Widget extends sf.object(\"Widget\", { id: sf.identifier }) {}\n\n\t\t\t\tconst { container } = await client.createContainer(schema, \"2\");\n\t\t\t\tawait container.attach();\n\t\t\t\tconst view = container.initialObjects.tree1.viewWith(\n\t\t\t\t\tnew TreeViewConfiguration({\n\t\t\t\t\t\tschema: sf.array(Widget),\n\t\t\t\t\t\tenableSchemaValidation: true,\n\t\t\t\t\t}),\n\t\t\t\t);\n\n\t\t\t\tview.initialize([new Widget({}), new Widget({ id: \"fidget\" })]);\n\n\t\t\t\tconst widget = view.root.at(0);\n\t\t\t\tassert(widget !== undefined);\n\t\t\t\tconst fidget = view.root.at(-1);\n\t\t\t\tassert(fidget !== undefined);\n\t\t\t\t// Test various Tree.* APIs and ensure they are working\n\t\t\t\tassert.equal(Tree.contains(view.root, widget), true);\n\t\t\t\tassert.equal(Tree.contains(fidget, widget), false);\n\t\t\t\tassert.equal(Tree.is(fidget, Widget), true);\n\t\t\t\tassert.equal(Tree.is(view.root, Widget), false);\n\t\t\t\tassert.equal(Tree.key(widget), 0);\n\t\t\t\tassert.equal(Tree.key(fidget), 1);\n\t\t\t\tassert.equal(Tree.parent(widget), view.root);\n\t\t\t\tassert.equal(Tree.schema(fidget), Widget);\n\t\t\t\tassert.equal(typeof Tree.shortId(widget), \"number\");\n\t\t\t\tassert.equal(Tree.shortId(fidget), \"fidget\");\n\t\t\t\tassert.equal(Tree.status(widget), TreeStatus.InDocument);\n\t\t\t});\n\n\t\t\tit(\"can listen to events on a recursive tree\", async () => {\n\t\t\t\tclass Doll extends sf.objectRecursive(\"Matryoshka\", {\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/explicit-function-return-type\n\t\t\t\t\tnested: sf.optionalRecursive([() => Doll]),\n\t\t\t\t}) {}\n\n\t\t\t\tconst { container } = await client.createContainer(schema, \"2\");\n\t\t\t\tawait container.attach();\n\t\t\t\tconst view = container.initialObjects.tree1.viewWith(\n\t\t\t\t\tnew TreeViewConfiguration({ schema: Doll, enableSchemaValidation: true }),\n\t\t\t\t);\n\n\t\t\t\t// These nodes in the initial tree are unhydrated...\n\t\t\t\tconst depth1 = new Doll({ nested: new Doll({}) });\n\t\t\t\tconst depth0 = new Doll({ nested: depth1 });\n\t\t\t\tview.initialize(depth0);\n\t\t\t\t// ...and confirmed to be the same nodes we get when we read the tree after initialization\n\t\t\t\tassert.equal(view.root, depth0);\n\t\t\t\tassert.equal(view.root.nested, depth1);\n\t\t\t\t// Record a list of the node events fired\n\t\t\t\tconst eventLog: string[] = [];\n\t\t\t\tTree.on(depth0, \"nodeChanged\", () => {\n\t\t\t\t\teventLog.push(\"depth0.nested changed\");\n\t\t\t\t});\n\t\t\t\tTree.on(depth1, \"nodeChanged\", () => {\n\t\t\t\t\teventLog.push(\"depth1.nested changed\");\n\t\t\t\t});\n\t\t\t\t// This event registration happens on the unhydrated (not yet inserted) node\n\t\t\t\tconst newDepth2 = new Doll({});\n\t\t\t\tTree.on(newDepth2, \"nodeChanged\", () => {\n\t\t\t\t\teventLog.push(\"depth2.nested changed\");\n\t\t\t\t});\n\t\t\t\t// Fire the events by doing mutations\n\t\t\t\tdepth1.nested = newDepth2; // \"depth1.nested changed\"\n\t\t\t\tassert.equal(depth1.nested, newDepth2);\n\t\t\t\tdepth1.nested.nested = new Doll({}); // \"depth2.nested changed\"\n\t\t\t\tdepth0.nested = new Doll({}); // \"depth0.nested changed\"\n\t\t\t\t// Ensure the events fired in the expected order\n\t\t\t\tassert.deepEqual(eventLog, [\n\t\t\t\t\t\"depth1.nested changed\",\n\t\t\t\t\t\"depth2.nested changed\",\n\t\t\t\t\t\"depth0.nested changed\",\n\t\t\t\t]);\n\t\t\t});\n\t\t}\n\t});\n}\n"]}
@@ -13,7 +13,7 @@ const testMatrix = getTestMatrix();
13
13
  for (const testOpts of testMatrix) {
14
14
  describe(`viewContainerVersion scenarios (${testOpts.variant})`, () => {
15
15
  const isEphemeral = testOpts.options.isEphemeral;
16
- const connectTimeoutMs = 10000;
16
+ const connectTimeoutMs = 10_000;
17
17
  let client;
18
18
  const schema = {
19
19
  initialObjects: {