Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,11 @@ export function manageRootClusterProxyHostAllowList({

let rootClusters: tshd.Cluster[];
try {
rootClusters = await tshdClient.listRootClusters(
cloneAbortSignal(abortController.signal)
const { response } = await tshdClient.listRootClusters(
{},
{ abort: cloneAbortSignal(abortController.signal) }
);
rootClusters = response.clusters;
} catch (error) {
if (isAbortError(error)) {
// Ignore abort errors. They will be logged by the gRPC client middleware.
Expand Down
4 changes: 3 additions & 1 deletion web/packages/teleterm/src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ async function getElectronGlobals(): Promise<ElectronGlobals> {
// All uses of tshClient must wait before updateTshdEventsServerAddress finishes to ensure that
// the client is ready. Otherwise we run into a risk of causing panics in tshd due to a missing
// tshd events client.
await tshClient.updateTshdEventsServerAddress(tshdEventsServerAddress);
await tshClient.updateTshdEventsServerAddress({
address: tshdEventsServerAddress,
});

return {
mainProcessClient,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ test('response error is cloned as an object for a unary call', async () => {
try {
// Normally we would simply await `client.fakeMethod()`, but jest doesn't support
// thenables https://github.com/jestjs/jest/issues/10501.
await client.fakeMethod().then();
await client.fakeMethod({}).then();
} catch (e) {
error = e;
}
Expand Down Expand Up @@ -180,7 +180,7 @@ test('response error is cloned as an object in a server streaming call', async (
fakeCall
)
);
const res = client.fakeMethod();
const res = client.fakeMethod({});
const onNext = jest.fn();
const onError = jest.fn();
res.responses.onNext(onNext);
Expand Down Expand Up @@ -232,7 +232,7 @@ test('response error is cloned as an object in a duplex call', async () => {
fakeCall
)
);
const res = client.fakeMethod();
const res = client.fakeMethod({});
const onNext = jest.fn();
const onError = jest.fn();
res.responses.onNext(onNext);
Expand Down
68 changes: 59 additions & 9 deletions web/packages/teleterm/src/services/tshd/cloneableClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
RpcError,
RpcOptions,
ServiceInfo,
FinishedUnaryCall,
} from '@protobuf-ts/runtime-rpc';

/**
Expand Down Expand Up @@ -149,25 +150,31 @@ export type CloneableClient<Client> = {
[Method in keyof Client]: Client[Method] extends (
...args: infer Args
) => infer ReturnType
? (
...args: { [K in keyof Args]: ReplaceRpcOptions<Args[K]> }
) => CloneableCallTypes<ReturnType>
? CloneableCallTypes<ReturnType>
: never;
};

type CloneableCallTypes<T> =
T extends UnaryCall<infer Req, infer Res>
? CloneableUnaryCall<Req, Res>
? (
input: Req,
options?: CloneableRpcOptions
) => CloneableUnaryCall<Req, Res>
: T extends ClientStreamingCall<infer Req, infer Res>
? CloneableClientStreamingCall<Req, Res>
? (
options?: CloneableRpcOptions
) => CloneableClientStreamingCall<Req, Res>
: T extends ServerStreamingCall<infer Req, infer Res>
? CloneableServerStreamingCall<Req, Res>
? (
input: Req,
options?: CloneableRpcOptions
) => CloneableServerStreamingCall<Req, Res>
: T extends DuplexStreamingCall<infer Req, infer Res>
? CloneableDuplexStreamingCall<Req, Res>
? (
options?: CloneableRpcOptions
) => CloneableDuplexStreamingCall<Req, Res>
: never;

type ReplaceRpcOptions<T> = T extends RpcOptions ? CloneableRpcOptions : T;

type CloneableUnaryCall<I extends object, O extends object> = Pick<
UnaryCall<I, O>,
'then'
Expand Down Expand Up @@ -367,3 +374,46 @@ function cloneThenRejection<TResult>(
return clonePromiseRejection(then(onFulfilled));
};
}

/**
* A helper for mocking unary calls. Creates a promise-like instance of a class which resolves to
* an object where only the response field contains something.
*
* The need for this helper stems from the fact that cloneableClient returns the whole then property
* of a unary call, so TypeScript expects the types to match.
*
* Alternatively, we could change cloneableClient to merely return the response property, plus maybe
* some other fields that we need.
*/
export class MockedUnaryCall<Response extends object>
implements CloneableUnaryCall<any, Response>
{
constructor(
public response: Response,
private error?: any
) {}

// The signature of then was autocompleted by TypeScript language server.
then<TResult1 = FinishedUnaryCall<any, Response>, TResult2 = never>(
onfulfilled?: (
value: FinishedUnaryCall<any, Response>
) => TResult1 | PromiseLike<TResult1>,
onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>
): Promise<TResult1 | TResult2> {
if (this.error) {
return Promise.reject(onrejected(this.error));
}

return Promise.resolve(
onfulfilled({
response: this.response,
method: undefined,
requestHeaders: undefined,
request: undefined,
headers: undefined,
status: undefined,
trailers: undefined,
})
);
}
}
Loading