-
Notifications
You must be signed in to change notification settings - Fork 532
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Test to validate out of band container create (#22594)
Adds a test that builds a wrapper over a document service factory and allows the actual creation of the container to done by a decoupled out of band function. In the test it is just another function, but this could be a call to a separate process or server. --------- Co-authored-by: jzaffiro <110866475+jzaffiro@users.noreply.github.com>
- Loading branch information
1 parent
2c9ff85
commit ff1b2c7
Showing
5 changed files
with
249 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
128 changes: 128 additions & 0 deletions
128
packages/test/local-server-tests/src/test/decoupledCreate.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
/*! | ||
* Copyright (c) Microsoft Corporation and contributors. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
|
||
import { strict as assert } from "assert"; | ||
|
||
import type { IRequest } from "@fluidframework/core-interfaces"; | ||
import type { FluidObject } from "@fluidframework/core-interfaces/internal"; | ||
import { | ||
IDocumentServiceFactory, | ||
type IResolvedUrl, | ||
type ISummaryTree, | ||
} from "@fluidframework/driver-definitions/internal"; | ||
import { | ||
LocalDocumentServiceFactory, | ||
LocalResolver, | ||
} from "@fluidframework/local-driver/internal"; | ||
import { | ||
LocalDeltaConnectionServer, | ||
type ILocalDeltaConnectionServer, | ||
} from "@fluidframework/server-local-server"; | ||
import type { ITestFluidObject } from "@fluidframework/test-utils/internal"; | ||
|
||
import { createLoader } from "../utils.js"; | ||
|
||
function createDSFWithOutOfBandCreate({ | ||
deltaConnectionServer, | ||
createContainerCallback, | ||
}: { | ||
deltaConnectionServer: ILocalDeltaConnectionServer; | ||
createContainerCallback: ( | ||
summary: ISummaryTree | undefined, | ||
resolvedUrl: IResolvedUrl, | ||
) => Promise<IRequest>; | ||
}) { | ||
return new Proxy<IDocumentServiceFactory>( | ||
new LocalDocumentServiceFactory(deltaConnectionServer), | ||
{ | ||
get: (t, p: keyof IDocumentServiceFactory, r) => { | ||
if (p === "createContainer") { | ||
return async (summary, resolvedUrl, logger, clientIsSummarizer) => { | ||
const url = await createContainerCallback(summary, resolvedUrl); | ||
// this is more like the load flow, where we resolve the url | ||
// and create the document service, and it works here, as | ||
// the callback actually does the work of creating the container. | ||
const resolver = new LocalResolver(); | ||
return t.createDocumentService( | ||
await resolver.resolve(url), | ||
logger, | ||
clientIsSummarizer, | ||
); | ||
}; | ||
} | ||
|
||
return Reflect.get(t, p, r); | ||
}, | ||
}, | ||
); | ||
} | ||
|
||
async function createContainerOutOfBand( | ||
deltaConnectionServer: ILocalDeltaConnectionServer, | ||
createContainerParams: { | ||
summary: ISummaryTree | undefined; | ||
resolvedUrl: IResolvedUrl; | ||
}, | ||
) { | ||
// this actually creates the container | ||
const { summary, resolvedUrl } = createContainerParams; | ||
const documentServiceFactory = new LocalDocumentServiceFactory(deltaConnectionServer); | ||
const documentService = await documentServiceFactory.createContainer(summary, resolvedUrl); | ||
const resolver = new LocalResolver(); | ||
return resolver.getAbsoluteUrl(documentService.resolvedUrl, ""); | ||
} | ||
|
||
describe("Scenario Test", () => { | ||
it("Create container via a decoupled out of band function and validate both attaching container and freshly loaded container both work.", async () => { | ||
const deltaConnectionServer = LocalDeltaConnectionServer.create(); | ||
|
||
/* | ||
* Setup a document service factory that uses a user specifiable createContainerCallback. | ||
* This callback could make a different server call, and just needs to return the url | ||
* of the newly created container/file. | ||
*/ | ||
let request: IRequest | undefined; | ||
const documentServiceFactory = createDSFWithOutOfBandCreate({ | ||
deltaConnectionServer, | ||
createContainerCallback: async (summary, resolvedUrl) => | ||
(request = { | ||
url: await createContainerOutOfBand(deltaConnectionServer, { | ||
summary, | ||
resolvedUrl, | ||
}), | ||
}), | ||
}); | ||
|
||
const { loader, codeDetails, urlResolver } = createLoader({ | ||
deltaConnectionServer, | ||
documentServiceFactory, | ||
}); | ||
|
||
const container = await loader.createDetachedContainer(codeDetails); | ||
|
||
{ | ||
// put a bit of data in the detached container so we can validate later | ||
const entryPoint: FluidObject<ITestFluidObject> = await container.getEntryPoint(); | ||
entryPoint.ITestFluidObject?.root.set("someKey", "someValue"); | ||
} | ||
|
||
// kicking off attach will end up calling the create container callback | ||
// which will actually create the container, and eventually finish the attach | ||
await container.attach(urlResolver.createCreateNewRequest("test")); | ||
|
||
{ | ||
// just reuse the same server, nothing else from the initial create | ||
const { loader: loader2 } = createLoader({ deltaConnectionServer }); | ||
|
||
// ensure and use the url we got from out of band create to load the container | ||
assert(request !== undefined); | ||
const container2 = await loader2.resolve(request); | ||
|
||
// ensure the newly loaded container has the data we expect. | ||
const entryPoint: FluidObject<ITestFluidObject> = await container2.getEntryPoint(); | ||
assert.strictEqual(entryPoint.ITestFluidObject?.root.get("someKey"), "someValue"); | ||
} | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,10 @@ | ||
{ | ||
"extends": "../../../../../common/build/build-common/tsconfig.test.node16.json", | ||
"compilerOptions": { | ||
"rootDir": "./", | ||
"outDir": "../../dist/test", | ||
"rootDir": "../", | ||
"outDir": "../../lib", | ||
"types": ["mocha"], | ||
"noUncheckedIndexedAccess": false, | ||
"exactOptionalPropertyTypes": false, | ||
}, | ||
"include": ["./**/*"], | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
/*! | ||
* Copyright (c) Microsoft Corporation and contributors. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
|
||
import { ContainerRuntimeFactoryWithDefaultDataStore } from "@fluidframework/aqueduct/internal"; | ||
import { | ||
type IFluidCodeDetails, | ||
type IHostLoader, | ||
type ILoaderOptions, | ||
type IRuntimeFactory, | ||
ICodeDetailsLoader, | ||
} from "@fluidframework/container-definitions/internal"; | ||
import { Loader } from "@fluidframework/container-loader/internal"; | ||
import type { | ||
IDocumentServiceFactory, | ||
IUrlResolver, | ||
} from "@fluidframework/driver-definitions/internal"; | ||
import { | ||
LocalDocumentServiceFactory, | ||
LocalResolver, | ||
} from "@fluidframework/local-driver/internal"; | ||
import { SharedMap } from "@fluidframework/map/internal"; | ||
import type { IFluidDataStoreFactory } from "@fluidframework/runtime-definitions/internal"; | ||
import { ILocalDeltaConnectionServer } from "@fluidframework/server-local-server"; | ||
import { TestFluidObjectFactory, LocalCodeLoader } from "@fluidframework/test-utils/internal"; | ||
|
||
/** | ||
* This allows the input object to be general, | ||
* and the default object to be specific, | ||
* which maintains strong typing for both inputs, and the defaults in the result. | ||
* So if a user specifies a value, that values type will be strongly specified on the Result. | ||
* However if the user does not specify an option input, the result will also get a strong | ||
* type based the default. | ||
*/ | ||
export type OptionalToDefault<TInput, TDefault> = { | ||
[P in keyof TDefault]: P extends keyof TInput | ||
? Exclude<TInput[P], undefined> extends never | ||
? TDefault[P] | ||
: TInput[P] | ||
: TDefault[P]; | ||
}; | ||
|
||
export interface CreateLoaderParams { | ||
deltaConnectionServer: ILocalDeltaConnectionServer; | ||
codeDetails?: IFluidCodeDetails; | ||
defaultDataStoreFactory?: IFluidDataStoreFactory; | ||
runtimeFactory?: IRuntimeFactory; | ||
codeLoader?: ICodeDetailsLoader; | ||
documentServiceFactory?: IDocumentServiceFactory; | ||
urlResolver?: IUrlResolver; | ||
options?: ILoaderOptions; | ||
} | ||
|
||
export interface CreateLoaderDefaultResults | ||
extends Required<Omit<CreateLoaderParams, "options">> { | ||
documentServiceFactory: LocalDocumentServiceFactory; | ||
urlResolver: LocalResolver; | ||
codeLoader: LocalCodeLoader; | ||
defaultDataStoreFactory: TestFluidObjectFactory; | ||
runtimeFactory: ContainerRuntimeFactoryWithDefaultDataStore; | ||
loader: IHostLoader; | ||
} | ||
|
||
export function createLoader<T extends CreateLoaderParams>( | ||
opts: T, | ||
): OptionalToDefault<T, CreateLoaderDefaultResults> { | ||
const deltaConnectionServer = opts.deltaConnectionServer; | ||
const documentServiceFactory = | ||
opts.documentServiceFactory ?? new LocalDocumentServiceFactory(deltaConnectionServer); | ||
|
||
const urlResolver = opts.urlResolver ?? new LocalResolver(); | ||
|
||
const defaultDataStoreFactory = | ||
opts.defaultDataStoreFactory ?? | ||
new TestFluidObjectFactory([["map", SharedMap.getFactory()]], "default"); | ||
|
||
const runtimeFactory = | ||
opts.runtimeFactory ?? | ||
new ContainerRuntimeFactoryWithDefaultDataStore({ | ||
defaultFactory: defaultDataStoreFactory, | ||
registryEntries: [ | ||
[defaultDataStoreFactory.type, Promise.resolve(defaultDataStoreFactory)], | ||
], | ||
}); | ||
|
||
const codeDetails = opts.codeDetails ?? { package: "test" }; | ||
|
||
const codeLoader = opts.codeLoader ?? new LocalCodeLoader([[codeDetails, runtimeFactory]]); | ||
|
||
const loader = new Loader({ | ||
codeLoader, | ||
documentServiceFactory, | ||
urlResolver, | ||
}); | ||
|
||
const rtn: OptionalToDefault<CreateLoaderParams, CreateLoaderDefaultResults> = { | ||
deltaConnectionServer, | ||
documentServiceFactory, | ||
urlResolver, | ||
codeDetails, | ||
defaultDataStoreFactory, | ||
runtimeFactory, | ||
codeLoader, | ||
loader, | ||
}; | ||
return rtn as OptionalToDefault<T, CreateLoaderDefaultResults>; | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.