Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 27 additions & 31 deletions packages/runtime/test-runtime-utils/src/insecureTokenProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,43 +38,39 @@ export class InsecureTokenProvider implements ITokenProvider {
* @defaultValue [ ScopeType.DocRead, ScopeType.DocWrite, ScopeType.SummaryWrite ]
*/
private readonly scopes?: ScopeType[],

/**
* Optional. Override of attach container scopes. If a param is not provided,
* InsecureTokenProvider will use the value of {@link scopes}.
*
* @remarks Common use of this parameter is to allow write for container
* attach and just read for all other access. Effectively can create a
* create and then read-only client.
*
* @param attachContainerScopes - See {@link @fluidframework/protocol-definitions#ITokenClaims.scopes}
*
* @defaultValue {@link scopes}
*/
private readonly attachContainerScopes?: ScopeType[],
) {}

/**
* {@inheritDoc @fluidframework/routerlicious-driver#ITokenProvider.fetchOrdererToken}
*/
public async fetchOrdererToken(
private readonly fetchToken = async (
tenantId: string,
documentId?: string,
): Promise<ITokenResponse> {
): Promise<ITokenResponse> => {
const generalScopes = this.scopes ?? [
ScopeType.DocRead,
ScopeType.DocWrite,
ScopeType.SummaryWrite,
];
const scopes = (documentId ? undefined : this.attachContainerScopes) ?? generalScopes;
return {
fromCache: true,
jwt: generateToken(
tenantId,
this.tenantKey,
this.scopes ?? [ScopeType.DocRead, ScopeType.DocWrite, ScopeType.SummaryWrite],
documentId,
this.user,
),
jwt: generateToken(tenantId, this.tenantKey, scopes, documentId, this.user),
};
}
};

/**
* {@inheritDoc @fluidframework/routerlicious-driver#ITokenProvider.fetchStorageToken}
*/
public async fetchStorageToken(
tenantId: string,
documentId: string,
): Promise<ITokenResponse> {
return {
fromCache: true,
jwt: generateToken(
tenantId,
this.tenantKey,
this.scopes ?? [ScopeType.DocRead, ScopeType.DocWrite, ScopeType.SummaryWrite],
documentId,
this.user,
),
};
}
public readonly fetchOrdererToken = this.fetchToken;

public readonly fetchStorageToken = this.fetchToken;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,27 @@
* Licensed under the MIT License.
*/

const importInternalModulesAllowedForTest = [
// Allow import of Fluid Framework external API exports.
"@fluidframework/*/{beta,alpha,legacy}",
"fluid-framework/{beta,alpha,legacy}",

// Allow import of Fluid Framework non-production test-utils APIs.
"@fluidframework/*/test-utils",

// Allow imports from sibling and ancestral sibling directories,
// but not from cousin directories. Parent is allowed but only
// because there isn't a known way to deny it.
"*/index.js",

// Should `telemetry-utils` provide support through `/test-utils` instead of `/internal`?
"@fluidframework/telemetry-utils/internal",

// Should `test-*utils` provide support through `/test-utils` instead of `/internal`?
"@fluidframework/test-utils/internal",
"@fluidframework/test-runtime-utils/internal",
];

module.exports = {
extends: [require.resolve("@fluidframework/eslint-config-fluid"), "prettier"],
rules: {
Expand All @@ -28,6 +49,12 @@ module.exports = {
rules: {
// Some deprecated APIs are permissible in tests; use `warn` to keep them visible
"import/no-deprecated": "warn",
"import/no-internal-modules": [
"error",
{
allow: importInternalModulesAllowedForTest,
},
],
},
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
"test:realsvc:tinylicious": "start-server-and-test start:tinylicious:test 7071 test:realsvc:tinylicious:run",
"test:realsvc:tinylicious:report": "npm run test:realsvc:tinylicious",
"test:realsvc:tinylicious:run": "npm run test:realsvc:azure:run -- --driver=t9s",
"test:realsvc:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:realsvc"
"test:realsvc:verbose": "cross-env FLUID_TEST_VERBOSE=msgs npm run test:realsvc",
"test:realsvc:veryverbose": "cross-env FLUID_TEST_VERBOSE=msgs+telem npm run test:realsvc"
},
"c8": {
"all": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,22 @@

import {
AzureClient,
type AzureClientPropsInternal,
type AzureLocalConnectionConfig,
type AzureRemoteConnectionConfig,
type ITelemetryBaseLogger,
} from "@fluidframework/azure-client/internal";
} from "@fluidframework/azure-client";
// eslint-disable-next-line import/no-internal-modules -- TODO consider a test exposure to avoid /internal
import type { AzureClientPropsInternal } from "@fluidframework/azure-client/internal";
import {
AzureClient as AzureClientLegacy,
type AzureLocalConnectionConfig as AzureLocalConnectionConfigLegacy,
type AzureRemoteConnectionConfig as AzureRemoteConnectionConfigLegacy,
type ITelemetryBaseLogger as ITelemetryBaseLoggerLegacy,
} from "@fluidframework/azure-client-legacy";
import type { IRuntimeFactory } from "@fluidframework/container-definitions/internal";
import type { IRuntimeFactory } from "@fluidframework/container-definitions/legacy";
import type { IConfigProviderBase } from "@fluidframework/core-interfaces";
import { ScopeType } from "@fluidframework/driver-definitions/internal";
import type {
CompatibilityMode,
ContainerSchema,
} from "@fluidframework/fluid-static/internal";
import { ScopeType } from "@fluidframework/driver-definitions/legacy";
import type { CompatibilityMode, ContainerSchema } from "@fluidframework/fluid-static";
import {
type MockLogger,
createChildLogger,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,22 @@
*/

import type { ITokenProvider } from "@fluidframework/azure-client";
import type { ScopeType } from "@fluidframework/driver-definitions/internal";
import type { ScopeType } from "@fluidframework/driver-definitions/legacy";
import { InsecureTokenProvider } from "@fluidframework/test-runtime-utils/internal";

export function createAzureTokenProvider(
id: string,
name: string,
scopes?: ScopeType[],
attachScopes?: ScopeType[],
): ITokenProvider {
const key = process.env.azure__fluid__relay__service__key as string;
if (key) {
const userConfig = {
id,
name,
};
return new InsecureTokenProvider(key, userConfig, scopes);
return new InsecureTokenProvider(key, userConfig, scopes, attachScopes);
} else {
throw new Error("Cannot create token provider.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@

import type { SignalListener } from "@fluid-experimental/data-objects";
import { EventEmitter } from "@fluid-internal/client-utils";
// eslint-disable-next-line import/no-internal-modules -- TODO consider a test exposure to avoid /internal
import { createDataObjectKind } from "@fluidframework/aqueduct/internal";
import {
DataObject,
DataObjectFactory,
type IDataObjectProps,
createDataObjectKind,
} from "@fluidframework/aqueduct/internal";
} from "@fluidframework/aqueduct/legacy";
import type { IErrorEvent, IFluidHandle } from "@fluidframework/core-interfaces";
import { SharedCounter } from "@fluidframework/counter/internal";
import type { Jsonable } from "@fluidframework/datastore-definitions/internal";
import type { IInboundSignalMessage } from "@fluidframework/runtime-definitions/internal";
import { SharedCounter } from "@fluidframework/counter/legacy";
import type { Jsonable } from "@fluidframework/datastore-definitions/legacy";
import type { IInboundSignalMessage } from "@fluidframework/runtime-definitions/legacy";

class TestDataObjectClass extends DataObject {
public static readonly Name = "@fluid-example/test-data-object";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type { AzureClient, AzureContainerServices } from "@fluidframework/azure-
import { AttachState } from "@fluidframework/container-definitions";
import { ConnectionState } from "@fluidframework/container-loader";
import type { ContainerSchema, IFluidContainer } from "@fluidframework/fluid-static";
import { SharedMap } from "@fluidframework/map/internal";
import { SharedMap } from "@fluidframework/map/legacy";
import { timeoutPromise } from "@fluidframework/test-utils/internal";
import type { AxiosResponse } from "axios";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,19 @@ import type { ConfigTypes, IConfigProviderBase } from "@fluidframework/core-inte
import {
MessageType,
type ISequencedDocumentMessage,
} from "@fluidframework/driver-definitions/internal";
} from "@fluidframework/driver-definitions/legacy";
// eslint-disable-next-line import/no-internal-modules -- TODO consider a test exposure to avoid /internal
import { isTreeContainerSchema } from "@fluidframework/fluid-static/internal";
import {
type ContainerSchema,
createTreeContainerRuntimeFactory,
isTreeContainerSchema,
type IFluidContainer,
} from "@fluidframework/fluid-static/internal";
import { SharedMap } from "@fluidframework/map/internal";
} from "@fluidframework/fluid-static/legacy";
import { SharedMap } from "@fluidframework/map/legacy";
import { SharedMap as SharedMapLegacy } from "@fluidframework/map-legacy";
import { MockLogger, UsageError } from "@fluidframework/telemetry-utils/internal";
import { timeoutPromise } from "@fluidframework/test-utils/internal";
import { SharedTree } from "@fluidframework/tree/internal";
import { SharedTree } from "@fluidframework/tree/legacy";
import type { AxiosResponse } from "axios";
import type { SinonSandbox } from "sinon";
import { createSandbox } from "sinon";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type { AzureClient } from "@fluidframework/azure-client";
import { ConnectionState } from "@fluidframework/container-loader";
import type { IFluidHandle } from "@fluidframework/core-interfaces";
import type { ContainerSchema, IFluidContainer } from "@fluidframework/fluid-static";
import { type ISharedMap, SharedMap } from "@fluidframework/map/internal";
import { type ISharedMap, SharedMap } from "@fluidframework/map/legacy";
import { timeoutPromise } from "@fluidframework/test-utils/internal";
import type { AxiosResponse } from "axios";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ import {
type AzureContainerServices,
type AzureLocalConnectionConfig,
type AzureRemoteConnectionConfig,
type ITelemetryBaseEvent,
} from "@fluidframework/azure-client";
import { AttachState } from "@fluidframework/container-definitions";
import { ConnectionState } from "@fluidframework/container-loader";
import { LogLevel } from "@fluidframework/core-interfaces";
import type { ScopeType } from "@fluidframework/driver-definitions/legacy";
import type { ContainerSchema, IFluidContainer } from "@fluidframework/fluid-static";
import {
getPresence,
Expand All @@ -22,28 +25,21 @@ import {
type LatestRaw,
type LatestMapRaw,
type StatesWorkspace,
// eslint-disable-next-line import/no-internal-modules
} from "@fluidframework/presence/beta";
import { InsecureTokenProvider } from "@fluidframework/test-runtime-utils/internal";
import { timeoutPromise } from "@fluidframework/test-utils/internal";

import type { ScopeType } from "../AzureClientFactory.js";
import { createAzureTokenProvider } from "../AzureTokenFactory.js";
import { TestDataObject } from "../TestDataObject.js";
import type { configProvider } from "../utils.js";

import type { MessageFromChild, MessageToChild } from "./messageTypes.js";
import type { MessageFromChild, MessageToChild, UserIdAndName } from "./messageTypes.js";

type MessageFromParent = MessageToChild;
type MessageToParent = Required<MessageFromChild>;
interface UserIdAndName {
id: string;
name: string;
}

const connectTimeoutMs = 10_000;
// Identifier given to child process
const process_id = process.argv[2];
const verbosity = process.argv[3] ?? "";

const useAzure = process.env.FLUID_CLIENT === "azure";
const tenantId = useAzure
Expand All @@ -54,14 +50,32 @@ if (useAzure && endPoint === undefined) {
throw new Error("Azure Fluid Relay service endpoint is missing");
}

function selectiveVerboseLog(event: ITelemetryBaseEvent, logLevel?: LogLevel): void {
if (event.eventName.includes(":Signal") || event.eventName.includes(":Join")) {
console.log(`[${process_id}] [${logLevel ?? LogLevel.default}]`, {
eventName: event.eventName,
details: event.details,
containerConnectionState: event.containerConnectionState,
});
} else if (
event.eventName.includes(":Container:") ||
event.eventName.includes(":Presence:")
) {
console.log(`[${process_id}] [${logLevel ?? LogLevel.default}]`, {
eventName: event.eventName,
containerConnectionState: event.containerConnectionState,
});
}
}

/**
* Get or create a Fluid container with Presence in initialObjects.
*/
const getOrCreatePresenceContainer = async (
id: string | undefined,
user: UserIdAndName,
config?: ReturnType<typeof configProvider>,
scopes?: ScopeType[],
createScopes?: ScopeType[],
): Promise<{
container: IFluidContainer;
presence: Presence;
Expand All @@ -74,16 +88,26 @@ const getOrCreatePresenceContainer = async (
const connectionProps: AzureRemoteConnectionConfig | AzureLocalConnectionConfig = useAzure
? {
tenantId,
tokenProvider: createAzureTokenProvider(user.id ?? "foo", user.name ?? "bar", scopes),
tokenProvider: createAzureTokenProvider(
user.id ?? "foo",
user.name ?? "bar",
scopes,
createScopes,
),
endpoint: endPoint,
type: "remote",
}
: {
tokenProvider: new InsecureTokenProvider("fooBar", user, scopes),
tokenProvider: new InsecureTokenProvider("fooBar", user, scopes, createScopes),
endpoint: "http://localhost:7071",
type: "local",
};
const client = new AzureClient({ connection: connectionProps });
const client = new AzureClient({
connection: connectionProps,
logger: {
send: verbosity.includes("telem") ? selectiveVerboseLog : () => {},
},
});
const schema: ContainerSchema = {
initialObjects: {
// A DataObject is added as otherwise fluid-static complains "Container cannot be initialized without any DataTypes"
Expand Down Expand Up @@ -122,7 +146,14 @@ const getOrCreatePresenceContainer = async (
};
function createSendFunction(): (msg: MessageToParent) => void {
if (process.send) {
return process.send.bind(process);
const sendFn = process.send.bind(process);
if (verbosity.includes("msgs")) {
return (msg: MessageToParent) => {
console.log(`[${process_id}] Sending`, msg);
sendFn(msg);
};
}
return sendFn;
}
throw new Error("process.send is not defined");
}
Expand Down Expand Up @@ -264,6 +295,9 @@ class MessageHandler {
}

public async onMessage(msg: MessageFromParent): Promise<void> {
if (verbosity.includes("msgs")) {
console.log(`[${process_id}] Received`, msg);
}
switch (msg.command) {
case "ping": {
this.handlePing();
Expand Down Expand Up @@ -325,6 +359,8 @@ class MessageHandler {
const { container, presence, containerId } = await getOrCreatePresenceContainer(
msg.containerId,
msg.user,
msg.scopes,
msg.createScopes,
);
this.container = container;
this.presence = presence;
Expand Down
Loading