platform/packages/openapi/test/v3_test.dart

442 lines
13 KiB
Dart
Raw Normal View History

2024-09-03 20:18:47 +00:00
import 'dart:convert';
import 'dart:io';
import 'package:protevus_openapi/v3.dart';
import 'package:test/test.dart';
void main() {
group("Components and resolution", () {
test("Can resolve object against registry", () {
final components = APIComponents();
components.schemas["foo"] = APISchemaObject.string();
final ref = APISchemaObject()
..referenceURI = Uri.parse("/components/schemas/foo");
final orig = components.resolve(ref);
expect(orig, isNotNull);
expect(orig!.type, APIType.string);
expect(ref.type, isNull);
final APISchemaObject? constructed = components
.resolveUri(Uri(path: "/components/schemas/foo")) as APISchemaObject?;
expect(constructed, isNotNull);
expect(constructed!.type, APIType.string);
});
test("Invalid ref uri format throws error", () {
final components = APIComponents();
try {
components.resolve(
APISchemaObject()
..referenceURI = Uri.parse("#/components/schemas/foo"),
);
expect(true, false);
} on ArgumentError catch (e) {
expect(e.message, contains("Invalid reference URI"));
}
try {
components.resolve(
APISchemaObject()..referenceURI = Uri.parse("#/components/schemas"),
);
expect(true, false);
} on ArgumentError catch (e) {
expect(e.message, contains("Invalid reference URI"));
}
try {
components.resolve(
APISchemaObject()..referenceURI = Uri.parse("/components/foobar/foo"),
);
expect(true, false);
} on ArgumentError catch (e) {
expect(e.message, contains("Invalid reference URI"));
}
});
test("Nonexisting component returns null", () {
final components = APIComponents();
expect(
components.resolve(
APISchemaObject()
..referenceURI = Uri.parse("/components/schemas/foo"),
),
isNull,
);
});
test("URIs are paths internally, but fragments when serialized", () {
final doc = APIDocument.fromMap({
"openapi": "3.0.0",
"info": {"title": "x", "version": "1"},
"paths": <String, dynamic>{},
"components": {
"schemas": {
"string": {
"type": "string",
},
"container": {"\$ref": "#/components/schemas/string"}
}
}
});
expect(
doc.components!.schemas["container"]!.referenceURI!
.toFilePath(windows: Platform.isWindows),
Uri.parse("/components/schemas/string")
.toFilePath(windows: Platform.isWindows),
);
doc.components!.schemas["other"] = APISchemaObject()
..referenceURI = Uri(path: "/components/schemas/container");
final out = doc.asMap();
expect(
out["components"]["schemas"]["container"][r"$ref"],
"#/components/schemas/string",
);
expect(
out["components"]["schemas"]["other"][r"$ref"],
"#/components/schemas/container",
);
});
});
group("Stripe spec", () {
APIDocument? doc;
Map<String, dynamic>? original;
setUpAll(() async {
final String config = await fetchStripExample();
final file = File(config);
final contents = file.readAsStringSync();
original = json.decode(contents) as Map<String, dynamic>;
if (original != null) {
doc = APIDocument.fromMap(original!);
}
});
test("Emits same document in asMap()", () {
expect(doc!.asMap(), original);
});
test("Has openapi version", () {
expect(doc!.version, "3.0.0");
});
test("Has info", () {
expect(doc!.info.title, "Stripe API");
expect(doc!.info.version, isNotNull);
expect(
doc!.info.description,
"The Stripe REST API. Please see https://stripe.com/docs/api for more details.",
);
expect(
doc!.info.termsOfServiceURL.toString(),
"https://stripe.com/us/terms/",
);
expect(doc!.info.contact!.email, "dev-platform@stripe.com");
expect(doc!.info.contact!.name, "Stripe Dev Platform Team");
expect(doc!.info.contact!.url.toString(), "https://stripe.com");
expect(doc!.info.license, isNull);
});
test("Has servers", () {
expect(doc!.servers, isNotNull);
expect(doc!.servers!.length, 1);
expect(doc!.servers!.first!.url.toString(), "https://api.stripe.com/");
expect(doc!.servers!.first!.description, isNull);
expect(doc!.servers!.first!.variables, isNull);
});
test("Tags", () {
expect(doc!.tags, isNull);
});
group("Paths", () {
test("Sample path 1", () {
expect(doc!.paths, isNotNull);
final p = doc!.paths!["/v1/transfers/{transfer}/reversals/{id}"];
expect(p, isNotNull);
expect(p!.description, isNull);
expect(p.operations.length, 2);
final getOp = p.operations["get"];
final getParams = getOp!.parameters;
final getResponses = getOp.responses;
expect(getOp.description, contains("10 most recent reversals"));
expect(getOp.id, "GetTransfersTransferReversalsId");
expect(getParams!.length, 3);
expect(getParams[0].location, APIParameterLocation.query);
expect(
getParams[0].description,
"Specifies which fields in the response should be expanded.",
);
expect(getParams[0].name, "expand");
expect(getParams[0].isRequired, false);
expect(getParams[0].schema!.type, APIType.array);
expect(getParams[0].schema!.items!.type, APIType.string);
expect(getParams[1].location, APIParameterLocation.path);
expect(getParams[1].name, "id");
expect(getParams[1].isRequired, true);
expect(getParams[1].schema!.type, APIType.string);
expect(getParams[2].location, APIParameterLocation.path);
expect(getParams[2].name, "transfer");
expect(getParams[2].isRequired, true);
expect(getParams[2].schema!.type, APIType.string);
expect(getResponses!.length, 2);
expect(getResponses["200"]!.content!.length, 1);
expect(
getResponses["200"]!
.content!["application/json"]!
.schema!
.referenceURI,
Uri.parse(
Uri.parse("#/components/schemas/transfer_reversal").fragment,
),
);
final resolvedElement = getResponses["200"]!
.content!["application/json"]!
.schema!
.properties!["balance_transaction"]!
.anyOf;
expect(
resolvedElement!.last!.properties!["amount"]!.type,
APIType.integer,
);
});
});
group("Components", () {});
test("Security requirement", () {
expect(doc!.security, isNotNull);
expect(doc!.security!.length, 2);
});
});
group("Schema", () {
test("Can read/emit schema object with additionalProperties=true", () {
final doc = APIDocument.fromMap({
"openapi": "3.0.0",
"info": {"title": "x", "version": "1"},
"paths": <String, dynamic>{},
"components": {
"schemas": {
"freeform": {"type": "object", "additionalProperties": true}
}
}
});
expect(
doc.components!.schemas["freeform"]!.additionalPropertyPolicy,
APISchemaAdditionalPropertyPolicy.freeForm,
);
expect(
doc.asMap()["components"]["schemas"]["freeform"]["type"],
"object",
);
expect(
doc.asMap()["components"]["schemas"]["freeform"]
["additionalProperties"],
true,
);
});
test("Can read/emit schema object with additionalProperties={}", () {
final doc = APIDocument.fromMap({
"openapi": "3.0.0",
"info": {"title": "x", "version": "1"},
"paths": <String, dynamic>{},
"components": {
"schemas": {
"freeform": {
"type": "object",
"additionalProperties": <String, dynamic>{}
}
}
}
});
expect(
doc.components!.schemas["freeform"]!.additionalPropertyPolicy,
APISchemaAdditionalPropertyPolicy.freeForm,
);
expect(
doc.asMap()["components"]["schemas"]["freeform"]["type"],
"object",
);
expect(
doc.asMap()["components"]["schemas"]["freeform"]
["additionalProperties"],
true,
);
});
});
group("Callbacks", () {});
group("'add' methods", () {
test("'addHeader'", () {
final resp = APIResponse("Response");
// when null
resp.addHeader(
"x",
APIHeader(schema: APISchemaObject.string(format: "initial")),
);
expect(resp.headers!["x"]!.schema!.format, "initial");
// add more than one
resp.addHeader(
"y",
APIHeader(schema: APISchemaObject.string(format: "second")),
);
expect(resp.headers!["x"]!.schema!.format, "initial");
expect(resp.headers!["y"]!.schema!.format, "second");
// cannot replace
resp.addHeader(
"y",
APIHeader(schema: APISchemaObject.string(format: "third")),
);
expect(resp.headers!["x"]!.schema!.format, "initial");
expect(resp.headers!["y"]!.schema!.format, "second");
});
test("'addContent'", () {
final resp = APIResponse("Response");
// when null
resp.addContent("x/a", APISchemaObject.string(format: "initial"));
expect(resp.content!["x/a"]!.schema!.format, "initial");
// add more than one
resp.addContent("y/a", APISchemaObject.string(format: "second"));
expect(resp.content!["x/a"]!.schema!.format, "initial");
expect(resp.content!["y/a"]!.schema!.format, "second");
// joins schema in oneOf if key exists
resp.addContent("y/a", APISchemaObject.string(format: "third"));
expect(resp.content!["x/a"]!.schema!.format, "initial");
expect(resp.content!["y/a"]!.schema!.oneOf!.first!.format, "second");
expect(resp.content!["y/a"]!.schema!.oneOf!.last!.format, "third");
});
test("'addResponse'", () {
final op = APIOperation("op", null);
// when null
op.addResponse(
200,
APIResponse.schema("OK", APISchemaObject.string(format: "initial")),
);
expect(
op.responses!["200"]!.content!["application/json"]!.schema!.format,
"initial",
);
// add more than one
op.addResponse(
400,
APIResponse.schema(
"KINDABAD",
APISchemaObject.string(format: "second"),
headers: {
"initial":
APIHeader(schema: APISchemaObject.string(format: "initial"))
},
),
);
expect(
op.responses!["200"]!.content!["application/json"]!.schema!.format,
"initial",
);
expect(
op.responses!["400"]!.content!["application/json"]!.schema!.format,
"second",
);
// join responses when key exists
op.addResponse(
400,
APIResponse.schema(
"REALBAD",
APISchemaObject.string(format: "third"),
headers: {
"second":
APIHeader(schema: APISchemaObject.string(format: "initial"))
},
),
);
expect(
op.responses!["200"]!.content!["application/json"]!.schema!.format,
"initial",
);
final r400 = op.responses!["400"]!;
expect(r400.description, contains("KINDABAD"));
expect(r400.description, contains("REALBAD"));
expect(r400.content!["application/json"]!.schema!.oneOf, isNotNull);
expect(r400.headers!["initial"], isNotNull);
expect(r400.headers!["second"], isNotNull);
});
test("'addResponse' guards against null value", () {
final op = APIOperation("op", null);
op.addResponse(
400,
APIResponse.schema(
"KINDABAD",
APISchemaObject.string(format: "second"),
),
);
expect(
op.responses!["400"]!.content!["application/json"]!.schema!.format,
"second",
);
op.addResponse(
400,
APIResponse.schema(
"REALBAD",
APISchemaObject.string(format: "third"),
),
);
expect(
op.responses!["400"]!.content!["application/json"]!.schema!.oneOf!
.length,
2,
);
});
});
}
Future<String> fetchStripExample() async {
// Spec file is too large for pub, and no other way to remove from pub publish
// than putting in .gitignore. Therefore, this file must be downloaded locally
// to this path, from this path: https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json
const config = "test/specs/stripe.json";
final configFile = File(config);
if (!configFile.existsSync()) {
if (!configFile.parent.existsSync()) {
Directory(configFile.parent.path).createSync(recursive: true);
}
const url =
'https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json';
final request = await HttpClient().getUrl(Uri.parse(url));
final response = await request.close();
await response.pipe(File(config).openWrite());
}
return config;
}