mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-08-28 06:14:03 +02:00
Create environments for OpenAPI server URLs (#590)
This commit is contained in:
@@ -26,6 +26,7 @@ type ImportedAuthentication = Pick<HttpRequest, "authentication" | "authenticati
|
|||||||
};
|
};
|
||||||
type AuthenticationVariableRegistry = Map<string, { name: string; value: string }>;
|
type AuthenticationVariableRegistry = Map<string, { name: string; value: string }>;
|
||||||
type OAuthVariableNames = { clientId: string; clientSecret: string };
|
type OAuthVariableNames = { clientId: string; clientSecret: string };
|
||||||
|
type ServerOverrideVariable = { name: string; value: string };
|
||||||
|
|
||||||
const HTTP_METHODS = ["delete", "get", "head", "options", "patch", "post", "put", "query", "trace"];
|
const HTTP_METHODS = ["delete", "get", "head", "options", "patch", "post", "put", "query", "trace"];
|
||||||
const BODY_CONTENT_TYPE_PREFERENCE = [
|
const BODY_CONTENT_TYPE_PREFERENCE = [
|
||||||
@@ -70,7 +71,9 @@ export async function convertOpenApi(contents: string): Promise<ImportPluginResp
|
|||||||
};
|
};
|
||||||
const authenticationVariables: AuthenticationVariableRegistry = new Map();
|
const authenticationVariables: AuthenticationVariableRegistry = new Map();
|
||||||
const oauthVariablesByScheme = buildOAuthVariablesByScheme(importState, spec);
|
const oauthVariablesByScheme = buildOAuthVariablesByScheme(importState, spec);
|
||||||
|
const serverOverrides = new Map<string, ServerOverrideVariable>();
|
||||||
const baseUrl = importBaseUrl(spec);
|
const baseUrl = importBaseUrl(spec);
|
||||||
|
const serverEnvironments = importServerEnvironments(spec);
|
||||||
// A local spec has no document URL against which OpenAPI's implicit "/"
|
// A local spec has no document URL against which OpenAPI's implicit "/"
|
||||||
// server can resolve. Keep the shared variable even when its initial value
|
// server can resolve. Keep the shared variable even when its initial value
|
||||||
// is empty so users can configure the host once instead of editing requests.
|
// is empty so users can configure the host once instead of editing requests.
|
||||||
@@ -129,6 +132,8 @@ export async function convertOpenApi(contents: string): Promise<ImportPluginResp
|
|||||||
pathItem,
|
pathItem,
|
||||||
pathParameters,
|
pathParameters,
|
||||||
requestBaseUrl,
|
requestBaseUrl,
|
||||||
|
serverOverrides,
|
||||||
|
useDynamicServerUrls: serverEnvironments.length > 1,
|
||||||
spec,
|
spec,
|
||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
folderId,
|
folderId,
|
||||||
@@ -140,21 +145,6 @@ export async function convertOpenApi(contents: string): Promise<ImportPluginResp
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (resources.httpRequests.some((request) => request.authenticationType === "oauth2")) {
|
if (resources.httpRequests.some((request) => request.authenticationType === "oauth2")) {
|
||||||
let globalEnvironment = resources.environments[0];
|
|
||||||
if (globalEnvironment == null) {
|
|
||||||
globalEnvironment = {
|
|
||||||
model: "environment",
|
|
||||||
id: importState.generateId("environment"),
|
|
||||||
workspaceId: workspace.id,
|
|
||||||
name: "Global Variables",
|
|
||||||
variables: [],
|
|
||||||
parentModel: "workspace",
|
|
||||||
parentId: null,
|
|
||||||
sortPriority: importState.nextSortPriority(),
|
|
||||||
};
|
|
||||||
resources.environments.push(globalEnvironment);
|
|
||||||
}
|
|
||||||
|
|
||||||
const variableNames = new Set(
|
const variableNames = new Set(
|
||||||
[...oauthVariablesByScheme.values()].flatMap(({ clientId, clientSecret }) => [
|
[...oauthVariablesByScheme.values()].flatMap(({ clientId, clientSecret }) => [
|
||||||
clientId,
|
clientId,
|
||||||
@@ -173,28 +163,39 @@ export async function convertOpenApi(contents: string): Promise<ImportPluginResp
|
|||||||
) {
|
) {
|
||||||
variableNames.add("baseUrlOrigin");
|
variableNames.add("baseUrlOrigin");
|
||||||
}
|
}
|
||||||
globalEnvironment.variables.push(...[...variableNames].map((name) => ({ name, value: "" })));
|
resources.environments[0]?.variables.push(
|
||||||
|
...[...variableNames].map((name) => ({ name, value: "" })),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resources.httpRequests.length === 0) return undefined;
|
if (resources.httpRequests.length === 0) return undefined;
|
||||||
|
|
||||||
if (authenticationVariables.size > 0) {
|
const baseEnvironment = resources.environments[0];
|
||||||
let environment = resources.environments[0];
|
if (baseEnvironment == null) return undefined;
|
||||||
if (environment == null) {
|
baseEnvironment.variables.push(...authenticationVariables.values());
|
||||||
environment = {
|
|
||||||
model: "environment",
|
const environmentSpecificVariables = baseEnvironment.variables;
|
||||||
id: importState.generateId("environment"),
|
baseEnvironment.variables = [...serverOverrides.values()];
|
||||||
workspaceId: workspace.id,
|
resources.environments.push(
|
||||||
name: "Global Variables",
|
...serverEnvironments.map(({ name, url }) => ({
|
||||||
variables: [],
|
model: "environment" as const,
|
||||||
parentModel: "workspace",
|
id: importState.generateId("environment"),
|
||||||
parentId: null,
|
workspaceId: workspace.id,
|
||||||
sortPriority: importState.nextSortPriority(),
|
name,
|
||||||
};
|
variables: environmentSpecificVariables.map((variable) => ({
|
||||||
resources.environments.push(environment);
|
...variable,
|
||||||
}
|
value:
|
||||||
environment.variables.push(...authenticationVariables.values());
|
variable.name === "baseUrl"
|
||||||
}
|
? url
|
||||||
|
: variable.name === "baseUrlOrigin"
|
||||||
|
? serverUrlOrigin(url)
|
||||||
|
: variable.value,
|
||||||
|
})),
|
||||||
|
parentModel: "environment" as const,
|
||||||
|
parentId: null,
|
||||||
|
sortPriority: importState.nextSortPriority(),
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
disambiguateNames(resources.httpRequests, routeLabels);
|
disambiguateNames(resources.httpRequests, routeLabels);
|
||||||
|
|
||||||
@@ -263,6 +264,8 @@ function importOperation({
|
|||||||
pathItem,
|
pathItem,
|
||||||
pathParameters,
|
pathParameters,
|
||||||
requestBaseUrl,
|
requestBaseUrl,
|
||||||
|
serverOverrides,
|
||||||
|
useDynamicServerUrls,
|
||||||
spec,
|
spec,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
folderId,
|
folderId,
|
||||||
@@ -276,6 +279,8 @@ function importOperation({
|
|||||||
pathItem: UnknownRecord;
|
pathItem: UnknownRecord;
|
||||||
pathParameters: unknown[];
|
pathParameters: unknown[];
|
||||||
requestBaseUrl: string;
|
requestBaseUrl: string;
|
||||||
|
serverOverrides: Map<string, ServerOverrideVariable>;
|
||||||
|
useDynamicServerUrls: boolean;
|
||||||
spec: UnknownRecord;
|
spec: UnknownRecord;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
folderId: string | null;
|
folderId: string | null;
|
||||||
@@ -294,6 +299,7 @@ function importOperation({
|
|||||||
oauthVariablesByScheme,
|
oauthVariablesByScheme,
|
||||||
operation,
|
operation,
|
||||||
spec,
|
spec,
|
||||||
|
useDynamicServerUrls,
|
||||||
});
|
});
|
||||||
const urlParameters = [
|
const urlParameters = [
|
||||||
...importUrlParameters({ importState, parameters }),
|
...importUrlParameters({ importState, parameters }),
|
||||||
@@ -327,7 +333,10 @@ function importOperation({
|
|||||||
name: importOperationName(operation, method, path),
|
name: importOperationName(operation, method, path),
|
||||||
description,
|
description,
|
||||||
method: method.toUpperCase(),
|
method: method.toUpperCase(),
|
||||||
url: buildOperationUrl(operationBaseUrl({ operation, pathItem, requestBaseUrl }), path),
|
url: buildOperationUrl(
|
||||||
|
operationBaseUrl({ operation, pathItem, requestBaseUrl, serverOverrides }),
|
||||||
|
path,
|
||||||
|
),
|
||||||
urlParameters,
|
urlParameters,
|
||||||
headers,
|
headers,
|
||||||
body: body.body,
|
body: body.body,
|
||||||
@@ -382,18 +391,26 @@ function operationBaseUrl({
|
|||||||
operation,
|
operation,
|
||||||
pathItem,
|
pathItem,
|
||||||
requestBaseUrl,
|
requestBaseUrl,
|
||||||
|
serverOverrides,
|
||||||
}: {
|
}: {
|
||||||
operation: UnknownRecord;
|
operation: UnknownRecord;
|
||||||
pathItem: UnknownRecord;
|
pathItem: UnknownRecord;
|
||||||
requestBaseUrl: string;
|
requestBaseUrl: string;
|
||||||
|
serverOverrides: Map<string, ServerOverrideVariable>;
|
||||||
}): string {
|
}): string {
|
||||||
for (const servers of [operation.servers, pathItem.servers]) {
|
for (const servers of [operation.servers, pathItem.servers]) {
|
||||||
const override = toArray(servers)
|
const override = toArray(servers)
|
||||||
.map((s) => interpolateServerUrl(toRecord(s)))
|
.map((s) => interpolateServerUrl(toRecord(s)))
|
||||||
.find((url) => url.length > 0);
|
.find((url) => url.length > 0);
|
||||||
// Overrides are inlined rather than shared, since only the spec-level base
|
if (override != null) {
|
||||||
// URL becomes the baseUrl variable
|
let variable = serverOverrides.get(override);
|
||||||
if (override != null) return override;
|
if (variable == null) {
|
||||||
|
const suffix = serverOverrides.size === 0 ? "" : String(serverOverrides.size + 1);
|
||||||
|
variable = { name: `serverUrl${suffix}`, value: override };
|
||||||
|
serverOverrides.set(override, variable);
|
||||||
|
}
|
||||||
|
return `\${[${variable.name}]}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return requestBaseUrl;
|
return requestBaseUrl;
|
||||||
}
|
}
|
||||||
@@ -646,6 +663,48 @@ function importBaseUrl(spec: UnknownRecord): string {
|
|||||||
return joinUrlParts(`${scheme}://${host}`, stringAt(spec, "basePath") ?? "");
|
return joinUrlParts(`${scheme}://${host}`, stringAt(spec, "basePath") ?? "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function importServerEnvironments(spec: UnknownRecord): { name: string; url: string }[] {
|
||||||
|
const servers = toArray(spec.servers)
|
||||||
|
.map(toRecord)
|
||||||
|
.map((server, index) => ({
|
||||||
|
name: stringAt(server, "description")?.trim() || `Server ${index + 1}`,
|
||||||
|
url: interpolateServerUrl(server),
|
||||||
|
}))
|
||||||
|
.filter(({ url }) => url.length > 0);
|
||||||
|
if (servers.length === 0) {
|
||||||
|
const hasSwaggerServer =
|
||||||
|
stringAt(spec, "swagger") === "2.0" &&
|
||||||
|
(stringAt(spec, "host") != null || stringAt(spec, "basePath") != null);
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: hasSwaggerServer ? "Server 1" : "Default",
|
||||||
|
url: hasSwaggerServer ? importBaseUrl(spec) : "",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const nameCounts = new Map<string, number>();
|
||||||
|
return servers.map((server) => {
|
||||||
|
const count = (nameCounts.get(server.name) ?? 0) + 1;
|
||||||
|
nameCounts.set(server.name, count);
|
||||||
|
return { ...server, name: count === 1 ? server.name : `${server.name} ${count}` };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function serverUrlOrigin(value: string): string {
|
||||||
|
try {
|
||||||
|
const origin = new URL(value).origin;
|
||||||
|
return origin === "null" ? "" : origin;
|
||||||
|
} catch {
|
||||||
|
if (!value.startsWith("//")) return "";
|
||||||
|
try {
|
||||||
|
return `//${new URL(`https:${value}`).host}`;
|
||||||
|
} catch {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function interpolateServerUrl(server: UnknownRecord): string {
|
function interpolateServerUrl(server: UnknownRecord): string {
|
||||||
let url = stringAt(server, "url") ?? "";
|
let url = stringAt(server, "url") ?? "";
|
||||||
for (const [name, variable] of Object.entries(toRecord(server.variables))) {
|
for (const [name, variable] of Object.entries(toRecord(server.variables))) {
|
||||||
@@ -931,12 +990,14 @@ function importAuthentication({
|
|||||||
oauthVariablesByScheme,
|
oauthVariablesByScheme,
|
||||||
operation,
|
operation,
|
||||||
spec,
|
spec,
|
||||||
|
useDynamicServerUrls,
|
||||||
}: {
|
}: {
|
||||||
authenticationVariables: AuthenticationVariableRegistry;
|
authenticationVariables: AuthenticationVariableRegistry;
|
||||||
importState: ImportState;
|
importState: ImportState;
|
||||||
oauthVariablesByScheme: Map<string, OAuthVariableNames>;
|
oauthVariablesByScheme: Map<string, OAuthVariableNames>;
|
||||||
operation: UnknownRecord;
|
operation: UnknownRecord;
|
||||||
spec: UnknownRecord;
|
spec: UnknownRecord;
|
||||||
|
useDynamicServerUrls: boolean;
|
||||||
}): ImportedAuthentication {
|
}): ImportedAuthentication {
|
||||||
const security = operation.security ?? spec.security;
|
const security = operation.security ?? spec.security;
|
||||||
if (Array.isArray(operation.security) && operation.security.length === 0) {
|
if (Array.isArray(operation.security) && operation.security.length === 0) {
|
||||||
@@ -968,6 +1029,7 @@ function importAuthentication({
|
|||||||
requirement: rawRequirement,
|
requirement: rawRequirement,
|
||||||
schemes,
|
schemes,
|
||||||
spec,
|
spec,
|
||||||
|
useDynamicServerUrls,
|
||||||
});
|
});
|
||||||
if (imported != null) return imported;
|
if (imported != null) return imported;
|
||||||
}
|
}
|
||||||
@@ -982,6 +1044,7 @@ function importSecurityRequirement({
|
|||||||
requirement,
|
requirement,
|
||||||
schemes,
|
schemes,
|
||||||
spec,
|
spec,
|
||||||
|
useDynamicServerUrls,
|
||||||
}: {
|
}: {
|
||||||
authenticationVariables: AuthenticationVariableRegistry;
|
authenticationVariables: AuthenticationVariableRegistry;
|
||||||
importState: ImportState;
|
importState: ImportState;
|
||||||
@@ -989,6 +1052,7 @@ function importSecurityRequirement({
|
|||||||
requirement: UnknownRecord;
|
requirement: UnknownRecord;
|
||||||
schemes: UnknownRecord;
|
schemes: UnknownRecord;
|
||||||
spec: UnknownRecord;
|
spec: UnknownRecord;
|
||||||
|
useDynamicServerUrls: boolean;
|
||||||
}): ImportedAuthentication | null {
|
}): ImportedAuthentication | null {
|
||||||
const entries = Object.entries(requirement);
|
const entries = Object.entries(requirement);
|
||||||
const headers: HttpRequestHeader[] = [];
|
const headers: HttpRequestHeader[] = [];
|
||||||
@@ -1022,6 +1086,7 @@ function importSecurityRequirement({
|
|||||||
clientId: "oauth_client_id",
|
clientId: "oauth_client_id",
|
||||||
clientSecret: "oauth_client_secret",
|
clientSecret: "oauth_client_secret",
|
||||||
},
|
},
|
||||||
|
useDynamicServerUrls,
|
||||||
);
|
);
|
||||||
} else if (type === "openIdConnect") {
|
} else if (type === "openIdConnect") {
|
||||||
const token = registerAuthenticationVariable(authenticationVariables, schemeName, "token");
|
const token = registerAuthenticationVariable(authenticationVariables, schemeName, "token");
|
||||||
@@ -1162,6 +1227,7 @@ function importOAuth2(
|
|||||||
rawScopes: unknown,
|
rawScopes: unknown,
|
||||||
baseUrl: string,
|
baseUrl: string,
|
||||||
variableNames: OAuthVariableNames,
|
variableNames: OAuthVariableNames,
|
||||||
|
useDynamicServerUrls: boolean,
|
||||||
): Pick<HttpRequest, "authentication" | "authenticationType"> | null {
|
): Pick<HttpRequest, "authentication" | "authenticationType"> | null {
|
||||||
const scope = toArray(rawScopes)
|
const scope = toArray(rawScopes)
|
||||||
.filter((s): s is string => typeof s === "string")
|
.filter((s): s is string => typeof s === "string")
|
||||||
@@ -1189,8 +1255,16 @@ function importOAuth2(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const { grantType, flow } of candidates) {
|
for (const { grantType, flow } of candidates) {
|
||||||
const authorizationUrl = resolveOAuthUrl(stringAt(flow, "authorizationUrl"), baseUrl);
|
const authorizationUrl = resolveOAuthUrl(
|
||||||
const accessTokenUrl = resolveOAuthUrl(stringAt(flow, "tokenUrl"), baseUrl);
|
stringAt(flow, "authorizationUrl"),
|
||||||
|
baseUrl,
|
||||||
|
useDynamicServerUrls,
|
||||||
|
);
|
||||||
|
const accessTokenUrl = resolveOAuthUrl(
|
||||||
|
stringAt(flow, "tokenUrl"),
|
||||||
|
baseUrl,
|
||||||
|
useDynamicServerUrls,
|
||||||
|
);
|
||||||
if (authorizationUrl == null && accessTokenUrl == null) continue;
|
if (authorizationUrl == null && accessTokenUrl == null) continue;
|
||||||
|
|
||||||
const grantPatch =
|
const grantPatch =
|
||||||
@@ -1229,7 +1303,11 @@ function importOAuth2(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveOAuthUrl(value: string | undefined, baseUrl: string): string | undefined {
|
function resolveOAuthUrl(
|
||||||
|
value: string | undefined,
|
||||||
|
baseUrl: string,
|
||||||
|
useDynamicServerUrls: boolean,
|
||||||
|
): string | undefined {
|
||||||
if (value == null) return undefined;
|
if (value == null) return undefined;
|
||||||
try {
|
try {
|
||||||
return new URL(value).toString();
|
return new URL(value).toString();
|
||||||
@@ -1237,6 +1315,13 @@ function resolveOAuthUrl(value: string | undefined, baseUrl: string): string | u
|
|||||||
// Relative endpoint; resolve it against the API base below.
|
// Relative endpoint; resolve it against the API base below.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (value.startsWith("//")) return value;
|
||||||
|
if (useDynamicServerUrls) {
|
||||||
|
return value.startsWith("/")
|
||||||
|
? `${templateVariable("baseUrlOrigin")}${value}`
|
||||||
|
: joinUrlParts(templateVariable("baseUrl"), value);
|
||||||
|
}
|
||||||
|
|
||||||
if (baseUrl.length > 0) {
|
if (baseUrl.length > 0) {
|
||||||
try {
|
try {
|
||||||
return new URL(value, `${trimTrailingSlashes(baseUrl)}/`).toString();
|
return new URL(value, `${trimTrailingSlashes(baseUrl)}/`).toString();
|
||||||
@@ -1246,7 +1331,6 @@ function resolveOAuthUrl(value: string | undefined, baseUrl: string): string | u
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.startsWith("//")) return value;
|
|
||||||
try {
|
try {
|
||||||
const placeholderOrigin = "https://openapi-import.invalid";
|
const placeholderOrigin = "https://openapi-import.invalid";
|
||||||
const relativeBase = new URL(`${trimTrailingSlashes(baseUrl)}/`, placeholderOrigin);
|
const relativeBase = new URL(`${trimTrailingSlashes(baseUrl)}/`, placeholderOrigin);
|
||||||
|
|||||||
@@ -11,6 +11,16 @@ exports[`importer-openapi > Snapshots real-world fixture apis-guru.yaml 1`] = `
|
|||||||
"parentId": null,
|
"parentId": null,
|
||||||
"parentModel": "workspace",
|
"parentModel": "workspace",
|
||||||
"sortPriority": 0,
|
"sortPriority": 0,
|
||||||
|
"variables": [],
|
||||||
|
"workspaceId": "GENERATE_ID::WORKSPACE_0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "GENERATE_ID::ENVIRONMENT_1",
|
||||||
|
"model": "environment",
|
||||||
|
"name": "Server 1",
|
||||||
|
"parentId": null,
|
||||||
|
"parentModel": "environment",
|
||||||
|
"sortPriority": 9,
|
||||||
"variables": [
|
"variables": [
|
||||||
{
|
{
|
||||||
"name": "baseUrl",
|
"name": "baseUrl",
|
||||||
@@ -326,6 +336,16 @@ exports[`importer-openapi > Snapshots real-world fixture httpbin.yaml 1`] = `
|
|||||||
"parentId": null,
|
"parentId": null,
|
||||||
"parentModel": "workspace",
|
"parentModel": "workspace",
|
||||||
"sortPriority": 0,
|
"sortPriority": 0,
|
||||||
|
"variables": [],
|
||||||
|
"workspaceId": "GENERATE_ID::WORKSPACE_0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "GENERATE_ID::ENVIRONMENT_1",
|
||||||
|
"model": "environment",
|
||||||
|
"name": "Server 1",
|
||||||
|
"parentId": null,
|
||||||
|
"parentModel": "environment",
|
||||||
|
"sortPriority": 90,
|
||||||
"variables": [
|
"variables": [
|
||||||
{
|
{
|
||||||
"name": "baseUrl",
|
"name": "baseUrl",
|
||||||
@@ -2612,6 +2632,16 @@ exports[`importer-openapi > Snapshots real-world fixture nasa-apod.yaml 1`] = `
|
|||||||
"parentId": null,
|
"parentId": null,
|
||||||
"parentModel": "workspace",
|
"parentModel": "workspace",
|
||||||
"sortPriority": 0,
|
"sortPriority": 0,
|
||||||
|
"variables": [],
|
||||||
|
"workspaceId": "GENERATE_ID::WORKSPACE_0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "GENERATE_ID::ENVIRONMENT_1",
|
||||||
|
"model": "environment",
|
||||||
|
"name": "Server 1",
|
||||||
|
"parentId": null,
|
||||||
|
"parentModel": "environment",
|
||||||
|
"sortPriority": 3,
|
||||||
"variables": [
|
"variables": [
|
||||||
{
|
{
|
||||||
"name": "baseUrl",
|
"name": "baseUrl",
|
||||||
@@ -2624,6 +2654,25 @@ exports[`importer-openapi > Snapshots real-world fixture nasa-apod.yaml 1`] = `
|
|||||||
],
|
],
|
||||||
"workspaceId": "GENERATE_ID::WORKSPACE_0",
|
"workspaceId": "GENERATE_ID::WORKSPACE_0",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "GENERATE_ID::ENVIRONMENT_2",
|
||||||
|
"model": "environment",
|
||||||
|
"name": "Server 2",
|
||||||
|
"parentId": null,
|
||||||
|
"parentModel": "environment",
|
||||||
|
"sortPriority": 4,
|
||||||
|
"variables": [
|
||||||
|
{
|
||||||
|
"name": "baseUrl",
|
||||||
|
"value": "http://api.nasa.gov/planetary",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "auth_api_key_key",
|
||||||
|
"value": "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"workspaceId": "GENERATE_ID::WORKSPACE_0",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
"folders": [
|
"folders": [
|
||||||
{
|
{
|
||||||
@@ -2715,6 +2764,16 @@ exports[`importer-openapi > Snapshots real-world fixture xkcd.yaml 1`] = `
|
|||||||
"parentId": null,
|
"parentId": null,
|
||||||
"parentModel": "workspace",
|
"parentModel": "workspace",
|
||||||
"sortPriority": 0,
|
"sortPriority": 0,
|
||||||
|
"variables": [],
|
||||||
|
"workspaceId": "GENERATE_ID::WORKSPACE_0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "GENERATE_ID::ENVIRONMENT_1",
|
||||||
|
"model": "environment",
|
||||||
|
"name": "Server 1",
|
||||||
|
"parentId": null,
|
||||||
|
"parentModel": "environment",
|
||||||
|
"sortPriority": 3,
|
||||||
"variables": [
|
"variables": [
|
||||||
{
|
{
|
||||||
"name": "baseUrl",
|
"name": "baseUrl",
|
||||||
|
|||||||
@@ -150,6 +150,11 @@ describe("importer-openapi", () => {
|
|||||||
expect(imported?.resources.environments).toEqual([
|
expect(imported?.resources.environments).toEqual([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
name: "Global Variables",
|
name: "Global Variables",
|
||||||
|
variables: [],
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
name: "Server 1",
|
||||||
|
parentModel: "environment",
|
||||||
variables: [
|
variables: [
|
||||||
{ name: "baseUrl", value: "https://api.example.com/v1" },
|
{ name: "baseUrl", value: "https://api.example.com/v1" },
|
||||||
{ name: "auth_token_auth_token", value: "" },
|
{ name: "auth_token_auth_token", value: "" },
|
||||||
@@ -252,6 +257,11 @@ describe("importer-openapi", () => {
|
|||||||
);
|
);
|
||||||
expect(imported?.resources.environments).toEqual([
|
expect(imported?.resources.environments).toEqual([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
|
name: "Global Variables",
|
||||||
|
variables: [],
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
name: "Server 1",
|
||||||
variables: [{ name: "baseUrl", value: "https://api.example.com/client/v4" }],
|
variables: [{ name: "baseUrl", value: "https://api.example.com/client/v4" }],
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
@@ -276,6 +286,11 @@ describe("importer-openapi", () => {
|
|||||||
expect(imported?.resources.environments).toEqual([
|
expect(imported?.resources.environments).toEqual([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
name: "Global Variables",
|
name: "Global Variables",
|
||||||
|
variables: [],
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
name: "Default",
|
||||||
|
parentModel: "environment",
|
||||||
variables: [{ name: "baseUrl", value: "" }],
|
variables: [{ name: "baseUrl", value: "" }],
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
@@ -304,8 +319,119 @@ describe("importer-openapi", () => {
|
|||||||
|
|
||||||
expect(imported?.resources.httpRequests.map((r) => r.url)).toEqual([
|
expect(imported?.resources.httpRequests.map((r) => r.url)).toEqual([
|
||||||
"${[baseUrl]}/root",
|
"${[baseUrl]}/root",
|
||||||
"https://path.example.com/path-level",
|
"${[serverUrl]}/path-level",
|
||||||
"https://operation.example.com/operation-level",
|
"${[serverUrl2]}/operation-level",
|
||||||
|
]);
|
||||||
|
expect(imported?.resources.environments).toEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
name: "Global Variables",
|
||||||
|
variables: [
|
||||||
|
{ name: "serverUrl", value: "https://path.example.com" },
|
||||||
|
{ name: "serverUrl2", value: "https://operation.example.com" },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
name: "Server 1",
|
||||||
|
variables: [{ name: "baseUrl", value: "https://root.example.com" }],
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Creates selectable environments for multiple OpenAPI servers", async () => {
|
||||||
|
const imported = await convertOpenApi(
|
||||||
|
JSON.stringify({
|
||||||
|
openapi: "3.0.4",
|
||||||
|
info: { title: "Server Environments Test", version: "1.0.0" },
|
||||||
|
servers: [
|
||||||
|
{ url: "https://api.example.com/v1", description: "Production" },
|
||||||
|
{ url: "https://sandbox.example.com/v1", description: "Sandbox" },
|
||||||
|
],
|
||||||
|
paths: {
|
||||||
|
"/oauth": { get: { security: [{ oauth: [] }], responses: {} } },
|
||||||
|
"/api-key": { get: { security: [{ apiKey: [] }], responses: {} } },
|
||||||
|
"/fixed": {
|
||||||
|
servers: [{ url: "https://fixed.example.com" }],
|
||||||
|
get: { responses: {} },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
securitySchemes: {
|
||||||
|
oauth: {
|
||||||
|
type: "oauth2",
|
||||||
|
flows: {
|
||||||
|
authorizationCode: {
|
||||||
|
authorizationUrl: "/oauth/authorize",
|
||||||
|
tokenUrl: "oauth/token",
|
||||||
|
scopes: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
apiKey: { type: "apiKey", in: "header", name: "X-API-Key" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(imported?.resources.environments).toEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
name: "Global Variables",
|
||||||
|
variables: [{ name: "serverUrl", value: "https://fixed.example.com" }],
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
name: "Production",
|
||||||
|
parentModel: "environment",
|
||||||
|
variables: [
|
||||||
|
{ name: "baseUrl", value: "https://api.example.com/v1" },
|
||||||
|
{ name: "oauth_client_id", value: "" },
|
||||||
|
{ name: "oauth_client_secret", value: "" },
|
||||||
|
{ name: "baseUrlOrigin", value: "https://api.example.com" },
|
||||||
|
{ name: "auth_api_key_key", value: "" },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
name: "Sandbox",
|
||||||
|
parentModel: "environment",
|
||||||
|
variables: [
|
||||||
|
{ name: "baseUrl", value: "https://sandbox.example.com/v1" },
|
||||||
|
{ name: "oauth_client_id", value: "" },
|
||||||
|
{ name: "oauth_client_secret", value: "" },
|
||||||
|
{ name: "baseUrlOrigin", value: "https://sandbox.example.com" },
|
||||||
|
{ name: "auth_api_key_key", value: "" },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
expect(imported?.resources.httpRequests[0]?.authentication).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
authorizationUrl: "${[baseUrlOrigin]}/oauth/authorize",
|
||||||
|
accessTokenUrl: "${[baseUrl]}/oauth/token",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Creates variables for path servers without a top-level server", async () => {
|
||||||
|
const imported = await convertOpenApi(
|
||||||
|
JSON.stringify({
|
||||||
|
openapi: "3.0.4",
|
||||||
|
info: { title: "Path Server Test", version: "1.0.0" },
|
||||||
|
paths: {
|
||||||
|
"/items": {
|
||||||
|
servers: [{ url: "https://path.example.com" }],
|
||||||
|
get: { responses: {} },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(imported?.resources.httpRequests[0]?.url).toBe("${[serverUrl]}/items");
|
||||||
|
expect(imported?.resources.environments).toEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
name: "Global Variables",
|
||||||
|
variables: [{ name: "serverUrl", value: "https://path.example.com" }],
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
name: "Default",
|
||||||
|
variables: [{ name: "baseUrl", value: "" }],
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -361,16 +487,22 @@ describe("importer-openapi", () => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
expect(imported?.resources.environments[0]?.variables).toEqual([
|
expect(imported?.resources.environments[0]?.variables).toEqual([]);
|
||||||
{ name: "baseUrl", value: "" },
|
expect(imported?.resources.environments[1]).toEqual(
|
||||||
{ name: "oauth_oauth_client_id", value: "" },
|
expect.objectContaining({
|
||||||
{ name: "oauth_oauth_client_secret", value: "" },
|
name: "Default",
|
||||||
{ name: "oauth_implicitOauth_client_id", value: "" },
|
variables: [
|
||||||
{ name: "oauth_implicitOauth_client_secret", value: "" },
|
{ name: "baseUrl", value: "" },
|
||||||
]);
|
{ name: "oauth_oauth_client_id", value: "" },
|
||||||
|
{ name: "oauth_oauth_client_secret", value: "" },
|
||||||
|
{ name: "oauth_implicitOauth_client_id", value: "" },
|
||||||
|
{ name: "oauth_implicitOauth_client_secret", value: "" },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Uses shared environment variables for OAuth2 client credentials", async () => {
|
test("Uses server environment variables for OAuth2 client credentials", async () => {
|
||||||
const imported = await convertOpenApi(
|
const imported = await convertOpenApi(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
openapi: "3.0.4",
|
openapi: "3.0.4",
|
||||||
@@ -399,10 +531,16 @@ describe("importer-openapi", () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(imported?.resources.environments[0]?.variables).toEqual([
|
expect(imported?.resources.environments).toEqual([
|
||||||
{ name: "baseUrl", value: "https://api.example.com" },
|
expect.objectContaining({ name: "Global Variables", variables: [] }),
|
||||||
{ name: "oauth_client_id", value: "" },
|
expect.objectContaining({
|
||||||
{ name: "oauth_client_secret", value: "" },
|
name: "Server 1",
|
||||||
|
variables: [
|
||||||
|
{ name: "baseUrl", value: "https://api.example.com" },
|
||||||
|
{ name: "oauth_client_id", value: "" },
|
||||||
|
{ name: "oauth_client_secret", value: "" },
|
||||||
|
],
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
expect(imported?.resources.httpRequests.map((request) => request.authentication)).toEqual([
|
expect(imported?.resources.httpRequests.map((request) => request.authentication)).toEqual([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
@@ -418,7 +556,7 @@ describe("importer-openapi", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Uses the shared base variable for OAuth endpoints with a path-only API base", async () => {
|
test("Uses the server environment origin for OAuth endpoints with a path-only API base", async () => {
|
||||||
const imported = await convertOpenApi(
|
const imported = await convertOpenApi(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
openapi: "3.0.4",
|
openapi: "3.0.4",
|
||||||
@@ -450,11 +588,17 @@ describe("importer-openapi", () => {
|
|||||||
accessTokenUrl: "${[baseUrlOrigin]}/oauth/token",
|
accessTokenUrl: "${[baseUrlOrigin]}/oauth/token",
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
expect(imported?.resources.environments[0]?.variables).toEqual([
|
expect(imported?.resources.environments).toEqual([
|
||||||
{ name: "baseUrl", value: "/api/v1" },
|
expect.objectContaining({ name: "Global Variables", variables: [] }),
|
||||||
{ name: "oauth_client_id", value: "" },
|
expect.objectContaining({
|
||||||
{ name: "oauth_client_secret", value: "" },
|
name: "Server 1",
|
||||||
{ name: "baseUrlOrigin", value: "" },
|
variables: [
|
||||||
|
{ name: "baseUrl", value: "/api/v1" },
|
||||||
|
{ name: "oauth_client_id", value: "" },
|
||||||
|
{ name: "oauth_client_secret", value: "" },
|
||||||
|
{ name: "baseUrlOrigin", value: "" },
|
||||||
|
],
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -691,6 +835,10 @@ describe("importer-openapi", () => {
|
|||||||
expect(imported?.resources.environments).toEqual([
|
expect(imported?.resources.environments).toEqual([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
name: "Global Variables",
|
name: "Global Variables",
|
||||||
|
variables: [],
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
name: "Server 1",
|
||||||
variables: [
|
variables: [
|
||||||
{ name: "baseUrl", value: "https://example.com/" },
|
{ name: "baseUrl", value: "https://example.com/" },
|
||||||
{ name: "auth_basic_auth_username", value: "" },
|
{ name: "auth_basic_auth_username", value: "" },
|
||||||
@@ -790,11 +938,17 @@ describe("importer-openapi", () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(imported?.resources.environments[0]?.variables).toEqual([
|
expect(imported?.resources.environments[0]?.variables).toEqual([]);
|
||||||
{ name: "baseUrl", value: "" },
|
expect(imported?.resources.environments[1]).toEqual(
|
||||||
{ name: "auth_api_key_key", value: "" },
|
expect.objectContaining({
|
||||||
{ name: "auth_api_key_key_2", value: "" },
|
name: "Default",
|
||||||
]);
|
variables: [
|
||||||
|
{ name: "baseUrl", value: "" },
|
||||||
|
{ name: "auth_api_key_key", value: "" },
|
||||||
|
{ name: "auth_api_key_key_2", value: "" },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
expect(imported?.resources.httpRequests).toEqual([
|
expect(imported?.resources.httpRequests).toEqual([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
authentication: expect.objectContaining({ value: "${[auth_api_key_key]}" }),
|
authentication: expect.objectContaining({ value: "${[auth_api_key_key]}" }),
|
||||||
|
|||||||
Reference in New Issue
Block a user