Skip to content

Commit

Permalink
Implement JS Client tests (#8109)
Browse files Browse the repository at this point in the history
* add msw setup and initialisation tests

* add changeset

* add walk_and_store_blobs improvements and add tests

* add changeset

* api_info tests

* add direct space URL link tests

* fix tests

* add view_api tests

* add post_message test

* tweak

* add spaces tests

* jwt and protocol tests

* add post_data tests

* test tweaks

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
  • Loading branch information
hannahblair and gradio-pr-bot committed Apr 30, 2024
1 parent 08b4e61 commit bed2f82
Show file tree
Hide file tree
Showing 28 changed files with 2,411 additions and 234 deletions.
7 changes: 7 additions & 0 deletions .changeset/ripe-shirts-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@gradio/app": minor
"@gradio/client": minor
"gradio": minor
---

feat:Implement JS Client tests
1 change: 1 addition & 0 deletions client/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
},
"dependencies": {
"bufferutil": "^4.0.7",
"msw": "^2.2.1",
"semiver": "^1.1.0",
"typescript": "^5.0.0",
"ws": "^8.13.0"
Expand Down
28 changes: 22 additions & 6 deletions client/js/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
} from "./helpers/init_helpers";
import { check_space_status } from "./helpers/spaces";
import { open_stream } from "./utils/stream";
import { API_INFO_ERROR_MSG, CONFIG_ERROR_MSG } from "./constants";

export class NodeBlob extends Blob {
constructor(blobParts?: BlobPart[], options?: BlobPropertyBag) {
Expand Down Expand Up @@ -95,7 +96,7 @@ export class Client {
event_data?: unknown
) => Promise<unknown>;
open_stream: () => void;
resolve_config: (endpoint: string) => Promise<Config | undefined>;
private resolve_config: (endpoint: string) => Promise<Config | undefined>;
constructor(app_reference: string, options: ClientOptions = {}) {
this.app_reference = app_reference;
this.options = options;
Expand Down Expand Up @@ -142,7 +143,7 @@ export class Client {
}
});
} catch (e) {
throw Error(`Could not resolve config: ${e}`);
throw Error(CONFIG_ERROR_MSG + (e as Error).message);
}

this.api_info = await this.view_api();
Expand Down Expand Up @@ -182,7 +183,7 @@ export class Client {
config = await this.resolve_config(`${http_protocol}//${host}`);

if (!config) {
throw new Error("Could not resolve app config");
throw new Error(CONFIG_ERROR_MSG);
}

return this.config_success(config);
Expand Down Expand Up @@ -224,7 +225,7 @@ export class Client {
try {
this.api_info = await this.view_api();
} catch (e) {
console.error(`Could not get API details: ${(e as Error).message}`);
console.error(API_INFO_ERROR_MSG + (e as Error).message);
}

return this.prepare_return_obj();
Expand All @@ -237,7 +238,7 @@ export class Client {
try {
this.config = await this._resolve_config();
if (!this.config) {
throw new Error("Could not resolve app config");
throw new Error(CONFIG_ERROR_MSG);
}

const _config = await this.config_success(this.config);
Expand All @@ -263,7 +264,7 @@ export class Client {
data: unknown[] | { binary: boolean; data: Record<string, any> }
): Promise<unknown> {
if (!this.config) {
throw new Error("Could not resolve app config");
throw new Error(CONFIG_ERROR_MSG);
}

const headers: {
Expand Down Expand Up @@ -363,4 +364,19 @@ export async function client(
return await Client.connect(app_reference, options);
}

/**
* @deprecated This method will be removed in v1.0. Use `Client.duplicate()` instead.
* Creates a duplicate of a space and returns a client instance for the duplicated space.
*
* @param {string} app_reference - The reference or URL to a Gradio space or app to duplicate.
* @param {DuplicateOptions} options - Configuration options for the client.
* @returns {Promise<Client>} A promise that resolves to a `Client` instance.
*/
export async function duplicate_space(
app_reference: string,
options: DuplicateOptions
): Promise<Client> {
return await Client.duplicate(app_reference, options);
}

export type ClientInstance = Client;
11 changes: 9 additions & 2 deletions client/js/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,19 @@ export const UPLOAD_URL = "upload";
export const LOGIN_URL = "login";
export const CONFIG_URL = "config";
export const API_INFO_URL = "info";
export const RUNTIME_URL = "runtime";
export const SLEEPTIME_URL = "sleeptime";
export const RAW_API_INFO_URL = "info?serialize=False";
export const SPACE_FETCHER_URL =
"https://gradio-space-api-fetcher-v2.hf.space/api";
export const RESET_URL = "reset";
export const SPACE_URL = "https://hf.space/{}";

// messages
export const QUEUE_FULL_MSG = "This application is too busy. Keep trying!";
export const BROKEN_CONNECTION_MSG = "Connection errored out.";
export const QUEUE_FULL_MSG =
"This application is currently busy. Please try again. ";
export const BROKEN_CONNECTION_MSG = "Connection errored out. ";
export const CONFIG_ERROR_MSG = "Could not resolve app config. ";
export const SPACE_STATUS_ERROR_MSG = "Could not get space status. ";
export const API_INFO_ERROR_MSG = "Could not get API info. ";
export const SPACE_METADATA_ERROR_MSG = "Space metadata could not be loaded. ";
8 changes: 4 additions & 4 deletions client/js/src/helpers/api_info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,16 @@ export async function process_endpoint(
{ headers }
);

if (res.status !== 200)
throw new Error("Space metadata could not be loaded.");
const _host = (await res.json()).host;

return {
space_id: app_reference,
...determine_protocol(_host)
};
} catch (e: any) {
throw new Error("Space metadata could not be loaded." + e.message);
} catch (e) {
throw new Error(
"Space metadata could not be loaded. " + (e as Error).message
);
}
}

Expand Down
74 changes: 46 additions & 28 deletions client/js/src/helpers/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {
Config,
EndpointInfo,
JsApiData,
ParamType
DataType
} from "../types";

export function update_object(
Expand All @@ -31,22 +31,22 @@ export function update_object(
}

export async function walk_and_store_blobs(
param: ParamType,
data: DataType,
type: string | undefined = undefined,
path: string[] = [],
root = false,
endpoint_info: EndpointInfo<ApiData | JsApiData> | undefined = undefined
): Promise<BlobRef[]> {
if (Array.isArray(param)) {
if (Array.isArray(data)) {
let blob_refs: BlobRef[] = [];

await Promise.all(
param.map(async (item) => {
data.map(async (item) => {
let new_path = path.slice();
new_path.push(item);

const array_refs = await walk_and_store_blobs(
param[item],
data[item],
root ? endpoint_info?.parameters[item]?.component || undefined : type,
new_path,
false,
Expand All @@ -58,44 +58,62 @@ export async function walk_and_store_blobs(
);

return blob_refs;
} else if (globalThis.Buffer && param instanceof globalThis.Buffer) {
} else if (
(globalThis.Buffer && data instanceof globalThis.Buffer) ||
data instanceof Blob
) {
const is_image = type === "Image";
return [
{
path: path,
blob: is_image ? false : new NodeBlob([param]),
blob: is_image ? false : new NodeBlob([data]),
type
}
];
} else if (typeof param === "object") {
} else if (typeof data === "object" && data !== null) {
let blob_refs: BlobRef[] = [];
for (let key in param) {
if (param.hasOwnProperty(key)) {
let new_path = path.slice();
new_path.push(key);
blob_refs = blob_refs.concat(
await walk_and_store_blobs(
// @ts-ignore
param[key],
undefined,
new_path,
false,
endpoint_info
)
);
}
for (const key of Object.keys(data) as (keyof typeof data)[]) {
const new_path = [...path, key];
const value = data[key];

blob_refs = blob_refs.concat(
await walk_and_store_blobs(
value,
undefined,
new_path,
false,
endpoint_info
)
);
}

if (
!blob_refs.length &&
!(
data instanceof Blob ||
data instanceof ArrayBuffer ||
data instanceof Uint8Array
)
) {
return [
{
path: path,
blob: new NodeBlob([JSON.stringify(data)]),
type: typeof data
}
];
}
return blob_refs;
}

return [];
}

export function skip_queue(id: number, config: Config): boolean {
return (
!(config?.dependencies?.[id]?.queue === null
? config.enable_queue
: config?.dependencies?.[id]?.queue) || false
);
if (config?.dependencies?.[id]?.queue !== null) {
return !config.dependencies[id].queue;
}
return !config.enable_queue;
}

// todo: add jsdoc for this function
Expand Down
7 changes: 3 additions & 4 deletions client/js/src/helpers/init_helpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Config } from "../types";
import { CONFIG_URL } from "../constants";
import { CONFIG_ERROR_MSG, CONFIG_URL } from "../constants";
import { Client } from "..";

/**
Expand Down Expand Up @@ -38,7 +38,6 @@ export async function get_jwt(

return jwt || false;
} catch (e) {
console.error(e);
return false;
}
}
Expand Down Expand Up @@ -89,10 +88,10 @@ export async function resolve_config(
config.root = endpoint;
return config;
}
throw new Error("Could not get config.");
throw new Error(CONFIG_ERROR_MSG);
}

throw new Error("No config or app endpoint found");
throw new Error(CONFIG_ERROR_MSG);
}

export function determine_protocol(endpoint: string): {
Expand Down
11 changes: 8 additions & 3 deletions client/js/src/helpers/spaces.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import {
RUNTIME_URL,
SLEEPTIME_URL,
SPACE_STATUS_ERROR_MSG
} from "../constants";
import type { SpaceStatusCallback } from "../types";

export async function check_space_status(
Expand All @@ -22,7 +27,7 @@ export async function check_space_status(
status_callback({
status: "error",
load_status: "error",
message: "Could not get space status",
message: SPACE_STATUS_ERROR_MSG,
detail: "NOT_FOUND"
});
return;
Expand Down Expand Up @@ -121,7 +126,7 @@ export async function get_space_hardware(

try {
const res = await fetch(
`https://huggingface.co/api/spaces/${space_id}/runtime`,
`https://huggingface.co/api/spaces/${space_id}/${RUNTIME_URL}`,
{ headers }
);

Expand Down Expand Up @@ -154,7 +159,7 @@ export async function set_space_timeout(

try {
const res = await fetch(
`https://huggingface.co/api/spaces/${space_id}/sleeptime`,
`https://huggingface.co/api/spaces/${space_id}/${SLEEPTIME_URL}`,
{
method: "POST",
headers: { "Content-Type": "application/json", ...headers },
Expand Down
2 changes: 1 addition & 1 deletion client/js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ export type {

// todo: remove in @gradio/client v1.0
export { client } from "./client";
export { duplicate } from "./utils/duplicate";
export { duplicate_space as duplicate } from "./client";

0 comments on commit bed2f82

Please sign in to comment.