From 740afe730b4ed46587f8a4b7fd482c3f1aa96e9b Mon Sep 17 00:00:00 2001
From: Tobias Ottenweller
Date: Sun, 8 Jun 2025 22:41:51 +0200
Subject: [PATCH 1/5] fix: prio for explicitly defined names
---
.../lib/src/naming/name_manager.dart | 16 +++-
.../test/src/naming/name_manager_test.dart | 94 ++++++++++++++++---
2 files changed, 95 insertions(+), 15 deletions(-)
diff --git a/packages/tonik_generate/lib/src/naming/name_manager.dart b/packages/tonik_generate/lib/src/naming/name_manager.dart
index aadbe2d..ddb95fb 100644
--- a/packages/tonik_generate/lib/src/naming/name_manager.dart
+++ b/packages/tonik_generate/lib/src/naming/name_manager.dart
@@ -58,9 +58,19 @@ class NameManager {
_logServerName(entry.value, entry.key);
}
- for (final model in models) {
- final name = modelName(model);
- _logModelName(name, model);
+ // Process models with explicit names first, then anonymous models
+ final modelsList = models.toList();
+ for (final model in modelsList) {
+ if (model is NamedModel && model.name != null) {
+ final name = modelName(model);
+ _logModelName(name, model);
+ }
+ }
+ for (final model in modelsList) {
+ if (model is! NamedModel || model.name == null) {
+ final name = modelName(model);
+ _logModelName(name, model);
+ }
}
for (final response in responses) {
diff --git a/packages/tonik_generate/test/src/naming/name_manager_test.dart b/packages/tonik_generate/test/src/naming/name_manager_test.dart
index 4102156..191c4c7 100644
--- a/packages/tonik_generate/test/src/naming/name_manager_test.dart
+++ b/packages/tonik_generate/test/src/naming/name_manager_test.dart
@@ -599,6 +599,76 @@ void main() {
expect(baseName2, baseName);
expect(identical(implementationNames, implementationNames2), isTrue);
});
+
+ group('model naming behavior', () {
+ late Context userContext;
+ late Context anonymousContext;
+
+ setUp(() {
+ userContext = Context.initial().pushAll([
+ 'components',
+ 'schemas',
+ 'user',
+ ]);
+ anonymousContext = Context.initial().pushAll([
+ 'components',
+ 'schemas',
+ 'anonymous',
+ ]);
+ });
+
+ test(
+ 'named model keeps original name and anonymous model gets Model suffix',
+ () {
+ final models = [
+ ClassModel(
+ name: 'User',
+ properties: const [],
+ context: userContext,
+ ),
+ ClassModel(properties: const [], context: userContext),
+ ];
+
+ manager.prime(
+ models: models,
+ responses: const [],
+ operations: const [],
+ tags: const [],
+ requestBodies: const [],
+ servers: const [],
+ );
+
+ expect(manager.modelName(models[0]), 'User');
+ expect(manager.modelName(models[1]), 'UserModel');
+ },
+ );
+
+ test(
+ 'named model takes precedence over anonymous model with same context',
+ () {
+ final models = [
+ ClassModel(properties: const [], context: userContext),
+ ClassModel(
+ name: 'User',
+ properties: const [],
+ context: userContext,
+ ),
+ ];
+
+ manager.prime(
+ models: models,
+ responses: const [],
+ operations: const [],
+ tags: const [],
+ requestBodies: const [],
+ servers: const [],
+ );
+
+ expect(manager.modelName(models[0]), 'UserModel');
+ expect(manager.modelName(models[1]), 'User');
+ },
+ );
+ });
});
group('Server names with list-based caching', () {
@@ -626,10 +696,10 @@ void main() {
const Server(url: 'https://staging.example.com', description: null),
const Server(url: 'https://dev.example.com', description: null),
];
-
+
// Identity should be different but content equal
expect(identical(servers, identicalContentServers), isFalse);
-
+
// Second call with different list but same content should use cache
final result2 = manager.serverNames(identicalContentServers);
@@ -637,18 +707,18 @@ void main() {
expect(result1.serverMap.length, result2.serverMap.length);
expect(result1.baseName, result2.baseName);
expect(result1.customName, result2.customName);
-
+
// The cache should only have one entry despite using two different lists
expect(manager.serverNamesCache.length, 1);
-
+
// Check that corresponding servers in each list have the same names
for (var i = 0; i < servers.length; i++) {
final server1 = servers[i];
final server2 = identicalContentServers[i];
-
+
final name1 = result1.serverMap[server1];
final name2 = result2.serverMap[server2];
-
+
expect(name1, name2);
}
});
@@ -674,28 +744,28 @@ void main() {
// Verify the server names are cached
expect(manager.serverNamesCache.length, 1);
-
+
// Verify the cache contains the correct key
expect(manager.serverNamesCache.containsKey(cacheKey), isTrue);
-
+
// Get the cached result
final cachedResult = manager.serverNamesCache[cacheKey]!;
-
+
// Verify the cached result has correct server map size
expect(cachedResult.serverMap.length, 2);
-
+
// Check that the servers are properly mapped to their expected names
for (final server in servers) {
final name = cachedResult.serverMap[server];
expect(name != null, isTrue);
-
+
if (server.url == 'https://api.example.com') {
expect(name!.startsWith('Api'), isTrue);
} else if (server.url == 'https://staging.example.com') {
expect(name!.startsWith('Staging'), isTrue);
}
}
-
+
// Verify custom name exists
expect(cachedResult.customName.contains('Custom'), isTrue);
});
From edfc24ef6c0ca83571b5ba6322a2f0cda6d20c5c Mon Sep 17 00:00:00 2001
From: Tobias Ottenweller
Date: Mon, 9 Jun 2025 12:04:25 +0200
Subject: [PATCH 2/5] fix: priority for exlict defined names of schemas
---
.../lib/src/naming/name_manager.dart | 23 +++++++++----------
.../test/src/naming/name_manager_test.dart | 6 -----
2 files changed, 11 insertions(+), 18 deletions(-)
diff --git a/packages/tonik_generate/lib/src/naming/name_manager.dart b/packages/tonik_generate/lib/src/naming/name_manager.dart
index ddb95fb..a3c7671 100644
--- a/packages/tonik_generate/lib/src/naming/name_manager.dart
+++ b/packages/tonik_generate/lib/src/naming/name_manager.dart
@@ -58,19 +58,18 @@ class NameManager {
_logServerName(entry.value, entry.key);
}
- // Process models with explicit names first, then anonymous models
- final modelsList = models.toList();
- for (final model in modelsList) {
- if (model is NamedModel && model.name != null) {
- final name = modelName(model);
- _logModelName(name, model);
- }
+ for (final model in models.where(
+ (m) => m is NamedModel && m.name != null,
+ )) {
+ final name = modelName(model);
+ _logModelName(name, model);
}
- for (final model in modelsList) {
- if (model is! NamedModel || model.name == null) {
- final name = modelName(model);
- _logModelName(name, model);
- }
+
+ for (final model in models.where(
+ (m) => m is! NamedModel || m.name == null,
+ )) {
+ final name = modelName(model);
+ _logModelName(name, model);
}
for (final response in responses) {
diff --git a/packages/tonik_generate/test/src/naming/name_manager_test.dart b/packages/tonik_generate/test/src/naming/name_manager_test.dart
index 191c4c7..2eb923e 100644
--- a/packages/tonik_generate/test/src/naming/name_manager_test.dart
+++ b/packages/tonik_generate/test/src/naming/name_manager_test.dart
@@ -602,7 +602,6 @@ void main() {
group('model naming behavior', () {
late Context userContext;
- late Context anonymousContext;
setUp(() {
userContext = Context.initial().pushAll([
@@ -610,11 +609,6 @@ void main() {
'schemas',
'user',
]);
- anonymousContext = Context.initial().pushAll([
- 'components',
- 'schemas',
- 'anonymous',
- ]);
});
test(
From 8413cf7676ef1d488ab0f84f7b223dbcf6dbdc24 Mon Sep 17 00:00:00 2001
From: Tobias Ottenweller
Date: Mon, 9 Jun 2025 12:04:43 +0200
Subject: [PATCH 3/5] chore: ignore all dart_tool folders
---
.gitignore | 2 +-
packages/tonik/README.md | 3 +--
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/.gitignore b/.gitignore
index 466097b..6a07362 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,5 @@
.DS_Store
-/.dart_tool
+.dart_tool
# FVM Version Cache
.fvm/
diff --git a/packages/tonik/README.md b/packages/tonik/README.md
index 7a7673d..5f80f6f 100644
--- a/packages/tonik/README.md
+++ b/packages/tonik/README.md
@@ -10,11 +10,10 @@
+
-
-
# Tonik
A Dart code generator for OpenAPI 3.0 and 3.1 specifications.
From d81cb132833babda4fb77bbfc7290829915fe8e7 Mon Sep 17 00:00:00 2001
From: Tobias Ottenweller
Date: Mon, 9 Jun 2025 12:05:02 +0200
Subject: [PATCH 4/5] test: add get an album 200 test
---
.../analysis_options.yaml | 12 +
.../imposter/imposter-config.json | 4 +
.../music_streaming_test/pubspec.lock | 468 ++++++++++++++++++
.../music_streaming_test/pubspec.yaml | 18 +
.../test/albums_test.dart | 193 ++++++++
.../test/test_helper.dart | 84 ++++
6 files changed, 779 insertions(+)
create mode 100644 integration_test/music_streaming/music_streaming_test/analysis_options.yaml
create mode 100644 integration_test/music_streaming/music_streaming_test/imposter/imposter-config.json
create mode 100644 integration_test/music_streaming/music_streaming_test/pubspec.lock
create mode 100644 integration_test/music_streaming/music_streaming_test/pubspec.yaml
create mode 100644 integration_test/music_streaming/music_streaming_test/test/albums_test.dart
create mode 100644 integration_test/music_streaming/music_streaming_test/test/test_helper.dart
diff --git a/integration_test/music_streaming/music_streaming_test/analysis_options.yaml b/integration_test/music_streaming/music_streaming_test/analysis_options.yaml
new file mode 100644
index 0000000..773cbeb
--- /dev/null
+++ b/integration_test/music_streaming/music_streaming_test/analysis_options.yaml
@@ -0,0 +1,12 @@
+
+include: package:very_good_analysis/analysis_options.yaml
+
+linter:
+ rules:
+ public_member_api_docs: false
+ avoid_print: false
+
+analyzer:
+ errors:
+ deprecated_member_use: ignore
+
\ No newline at end of file
diff --git a/integration_test/music_streaming/music_streaming_test/imposter/imposter-config.json b/integration_test/music_streaming/music_streaming_test/imposter/imposter-config.json
new file mode 100644
index 0000000..ad98044
--- /dev/null
+++ b/integration_test/music_streaming/music_streaming_test/imposter/imposter-config.json
@@ -0,0 +1,4 @@
+{
+ "plugin": "openapi",
+ "specFile": "../../openapi.yaml"
+}
\ No newline at end of file
diff --git a/integration_test/music_streaming/music_streaming_test/pubspec.lock b/integration_test/music_streaming/music_streaming_test/pubspec.lock
new file mode 100644
index 0000000..f7a4c2d
--- /dev/null
+++ b/integration_test/music_streaming/music_streaming_test/pubspec.lock
@@ -0,0 +1,468 @@
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+ _fe_analyzer_shared:
+ dependency: transitive
+ description:
+ name: _fe_analyzer_shared
+ sha256: e55636ed79578b9abca5fecf9437947798f5ef7456308b5cb85720b793eac92f
+ url: "https://pub.dev"
+ source: hosted
+ version: "82.0.0"
+ analyzer:
+ dependency: transitive
+ description:
+ name: analyzer
+ sha256: "904ae5bb474d32c38fb9482e2d925d5454cda04ddd0e55d2e6826bc72f6ba8c0"
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.4.5"
+ args:
+ dependency: transitive
+ description:
+ name: args
+ sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.7.0"
+ async:
+ dependency: transitive
+ description:
+ name: async
+ sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.13.0"
+ big_decimal:
+ dependency: transitive
+ description:
+ name: big_decimal
+ sha256: "301158ec5a646d1e1a0ca7a97fbfab7be18a8df700adb7f7cb9c4149e75c8f0c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.5.0"
+ boolean_selector:
+ dependency: transitive
+ description:
+ name: boolean_selector
+ sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.2"
+ checked_yaml:
+ dependency: transitive
+ description:
+ name: checked_yaml
+ sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.3"
+ cli_config:
+ dependency: transitive
+ description:
+ name: cli_config
+ sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.0"
+ collection:
+ dependency: transitive
+ description:
+ name: collection
+ sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.19.1"
+ convert:
+ dependency: transitive
+ description:
+ name: convert
+ sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.2"
+ coverage:
+ dependency: transitive
+ description:
+ name: coverage
+ sha256: "4b8701e48a58f7712492c9b1f7ba0bb9d525644dd66d023b62e1fc8cdb560c8a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.14.0"
+ crypto:
+ dependency: transitive
+ description:
+ name: crypto
+ sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.6"
+ dio:
+ dependency: "direct main"
+ description:
+ name: dio
+ sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9"
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.8.0+1"
+ dio_web_adapter:
+ dependency: transitive
+ description:
+ name: dio_web_adapter
+ sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.1"
+ file:
+ dependency: transitive
+ description:
+ name: file
+ sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.0.1"
+ frontend_server_client:
+ dependency: transitive
+ description:
+ name: frontend_server_client
+ sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.0.0"
+ glob:
+ dependency: transitive
+ description:
+ name: glob
+ sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.3"
+ http_multi_server:
+ dependency: transitive
+ description:
+ name: http_multi_server
+ sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.2.2"
+ http_parser:
+ dependency: transitive
+ description:
+ name: http_parser
+ sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.1.2"
+ io:
+ dependency: transitive
+ description:
+ name: io
+ sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.5"
+ js:
+ dependency: transitive
+ description:
+ name: js
+ sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.7.2"
+ json_annotation:
+ dependency: transitive
+ description:
+ name: json_annotation
+ sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.9.0"
+ lints:
+ dependency: transitive
+ description:
+ name: lints
+ sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.0.0"
+ logging:
+ dependency: transitive
+ description:
+ name: logging
+ sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.0"
+ matcher:
+ dependency: transitive
+ description:
+ name: matcher
+ sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.12.17"
+ meta:
+ dependency: transitive
+ description:
+ name: meta
+ sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.17.0"
+ mime:
+ dependency: transitive
+ description:
+ name: mime
+ sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.0"
+ music_streaming_api:
+ dependency: "direct main"
+ description:
+ path: "../music_streaming_api"
+ relative: true
+ source: path
+ version: "1.0.0"
+ node_preamble:
+ dependency: transitive
+ description:
+ name: node_preamble
+ sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.2"
+ package_config:
+ dependency: transitive
+ description:
+ name: package_config
+ sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.0"
+ path:
+ dependency: "direct main"
+ description:
+ name: path
+ sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.9.1"
+ pool:
+ dependency: transitive
+ description:
+ name: pool
+ sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.5.1"
+ pub_semver:
+ dependency: transitive
+ description:
+ name: pub_semver
+ sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.0"
+ pubspec_parse:
+ dependency: transitive
+ description:
+ name: pubspec_parse
+ sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.5.0"
+ shelf:
+ dependency: transitive
+ description:
+ name: shelf
+ sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.4.2"
+ shelf_packages_handler:
+ dependency: transitive
+ description:
+ name: shelf_packages_handler
+ sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.2"
+ shelf_static:
+ dependency: transitive
+ description:
+ name: shelf_static
+ sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.3"
+ shelf_web_socket:
+ dependency: transitive
+ description:
+ name: shelf_web_socket
+ sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.0"
+ source_map_stack_trace:
+ dependency: transitive
+ description:
+ name: source_map_stack_trace
+ sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.2"
+ source_maps:
+ dependency: transitive
+ description:
+ name: source_maps
+ sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.10.13"
+ source_span:
+ dependency: transitive
+ description:
+ name: source_span
+ sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.10.1"
+ stack_trace:
+ dependency: transitive
+ description:
+ name: stack_trace
+ sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.12.1"
+ stream_channel:
+ dependency: transitive
+ description:
+ name: stream_channel
+ sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.4"
+ string_scanner:
+ dependency: transitive
+ description:
+ name: string_scanner
+ sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.4.1"
+ term_glyph:
+ dependency: transitive
+ description:
+ name: term_glyph
+ sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.2"
+ test:
+ dependency: "direct dev"
+ description:
+ name: test
+ sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.26.2"
+ test_api:
+ dependency: transitive
+ description:
+ name: test_api
+ sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.7.6"
+ test_core:
+ dependency: transitive
+ description:
+ name: test_core
+ sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.6.11"
+ tonik_util:
+ dependency: "direct main"
+ description:
+ name: tonik_util
+ sha256: f16c86d5349fac40893d1d8ae87f6507192d8886f16ba6acefeabef4a61ae3ef
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.0.5"
+ typed_data:
+ dependency: transitive
+ description:
+ name: typed_data
+ sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.4.0"
+ very_good_analysis:
+ dependency: "direct dev"
+ description:
+ name: very_good_analysis
+ sha256: "62d2b86d183fb81b2edc22913d9f155d26eb5cf3855173adb1f59fac85035c63"
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.0.0"
+ vm_service:
+ dependency: transitive
+ description:
+ name: vm_service
+ sha256: "6f82e9ee8e7339f5d8b699317f6f3afc17c80a68ebef1bc0d6f52a678c14b1e6"
+ url: "https://pub.dev"
+ source: hosted
+ version: "15.0.1"
+ watcher:
+ dependency: transitive
+ description:
+ name: watcher
+ sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.1"
+ web:
+ dependency: transitive
+ description:
+ name: web
+ sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.1"
+ web_socket:
+ dependency: transitive
+ description:
+ name: web_socket
+ sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.1"
+ web_socket_channel:
+ dependency: transitive
+ description:
+ name: web_socket_channel
+ sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.3"
+ webkit_inspection_protocol:
+ dependency: transitive
+ description:
+ name: webkit_inspection_protocol
+ sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.1"
+ yaml:
+ dependency: transitive
+ description:
+ name: yaml
+ sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.3"
+sdks:
+ dart: ">=3.8.0 <4.0.0"
diff --git a/integration_test/music_streaming/music_streaming_test/pubspec.yaml b/integration_test/music_streaming/music_streaming_test/pubspec.yaml
new file mode 100644
index 0000000..b7b61fb
--- /dev/null
+++ b/integration_test/music_streaming/music_streaming_test/pubspec.yaml
@@ -0,0 +1,18 @@
+name: music_streaming_test
+description: Integration tests for the music streaming API
+version: 1.0.0
+publish_to: none
+
+environment:
+ sdk: ^3.8.0
+
+dependencies:
+ dio: ^5.8.0
+ music_streaming_api:
+ path: ../music_streaming_api
+ path: ^1.8.3
+ tonik_util: ^0.0.2
+
+dev_dependencies:
+ test: ^1.24.0
+ very_good_analysis: ^7.0.0
diff --git a/integration_test/music_streaming/music_streaming_test/test/albums_test.dart b/integration_test/music_streaming/music_streaming_test/test/albums_test.dart
new file mode 100644
index 0000000..190105f
--- /dev/null
+++ b/integration_test/music_streaming/music_streaming_test/test/albums_test.dart
@@ -0,0 +1,193 @@
+import 'package:dio/dio.dart';
+import 'package:music_streaming_api/music_streaming_api.dart';
+import 'package:test/test.dart';
+import 'package:tonik_util/tonik_util.dart';
+
+import 'test_helper.dart';
+
+void main() {
+ const port = 8080;
+ const baseUrl = 'http://localhost:$port/v1';
+
+ late ImposterServer imposterServer;
+
+ setUpAll(() async {
+ imposterServer = ImposterServer(port: port);
+ await setupImposterServer(imposterServer);
+ });
+
+ AlbumsApi buildAlbumsApi({required String responseStatus}) {
+ return AlbumsApi(
+ CustomServer(
+ baseUrl: baseUrl,
+ serverConfig: ServerConfig(
+ baseOptions: BaseOptions(
+ headers: {'X-Response-Status': responseStatus},
+ ),
+ ),
+ ),
+ );
+ }
+
+ group('getAnAlbum', () {
+ test('200', () async {
+ final albumsApi = buildAlbumsApi(responseStatus: '200');
+
+ final response = await albumsApi.getAnAlbum(id: '123');
+
+ expect(response, isA>());
+ final success = response as TonikSuccess;
+ expect(success.response.statusCode, 200);
+ expect(success.value, isA());
+
+ final value = success.value as GetAnAlbumResponse200;
+ expect(value.body, isA());
+ final albumBase = value.body.albumBase;
+
+ expect(albumBase, isA());
+ expect(albumBase.albumType, isA());
+ expect(AlbumBaseAlbumType.values.map((v) => v.rawValue), [
+ 'album',
+ 'single',
+ 'compilation',
+ ]);
+ expect(albumBase.totalTracks, isA());
+ expect(albumBase.availableMarkets, isA>());
+ expect(albumBase.externalUrls, isA());
+
+ expect(albumBase.href, isA());
+ expect(albumBase.id, isA());
+ expect(albumBase.images, isA>());
+ expect(albumBase.name, isA());
+ expect(albumBase.releaseDate, isA());
+ expect(
+ albumBase.releaseDatePrecision,
+ isA(),
+ );
+ expect(AlbumBaseReleaseDatePrecision.values.map((v) => v.rawValue), [
+ 'year',
+ 'month',
+ 'day',
+ ]);
+ expect(albumBase.restrictions, isA());
+ expect(albumBase.$type, isA());
+ expect(AlbumBaseType.values.map((v) => v.rawValue), ['album']);
+ expect(albumBase.uri, isA());
+
+ final externalUrls = albumBase.externalUrls.externalUrlObject;
+ expect(externalUrls.spotify, isA());
+
+ final image = albumBase.images.first;
+ expect(image.url, isA());
+ expect(image.height, isA());
+ expect(image.width, isA());
+
+ final releaseDatePrecision =
+ albumBase.restrictions?.albumRestrictionObject;
+ expect(releaseDatePrecision?.reason, isA());
+ expect(AlbumRestrictionObjectReason.values.map((v) => v.rawValue), [
+ 'market',
+ 'product',
+ 'explicit',
+ ]);
+
+ final albumObject = value.body.albumObjectModel;
+ expect(albumObject, isA());
+
+ // Note: api document intends to have properties of
+ // [SimplifiedArtistObject] to be non-nullable, but
+ // required is defined on wrong level and will get
+ // ingnored by tonic.
+ expect(albumObject.artists, isA?>());
+ final artist = albumObject.artists?.first;
+ expect(artist?.externalUrls, isA());
+ expect(artist?.externalUrls?.externalUrlObject, isA());
+ expect(artist?.externalUrls?.externalUrlObject.spotify, isA());
+ expect(artist?.href, isA());
+ expect(artist?.id, isA());
+ expect(artist?.name, isA());
+ expect(artist?.$type, isA());
+ expect(SimplifiedArtistObjectType.values.map((v) => v.rawValue), [
+ 'artist',
+ ]);
+ expect(artist?.uri, isA());
+
+ expect(albumObject.tracks, isA());
+ expect(
+ albumObject.tracks?.pagingSimplifiedTrackObject,
+ isA(),
+ );
+ final track = albumObject.tracks?.pagingSimplifiedTrackObject;
+ expect(track?.pagingObject, isA());
+ expect(track?.pagingObject.href, isA());
+ expect(track?.pagingObject.limit, isA());
+ expect(track?.pagingObject.next, isA());
+ expect(track?.pagingObject.offset, isA());
+ expect(track?.pagingObject.previous, isA());
+ expect(track?.pagingObject.total, isA());
+
+ expect(
+ track?.pagingSimplifiedTrackObjectModel,
+ isA(),
+ );
+ expect(
+ track?.pagingSimplifiedTrackObjectModel.items,
+ isA>(),
+ );
+ final trackItem = track?.pagingSimplifiedTrackObjectModel.items?.first;
+ expect(trackItem?.artists, isA?>());
+ expect(trackItem?.availableMarkets, isA?>());
+ expect(trackItem?.discNumber, isA());
+ expect(trackItem?.durationMs, isA());
+ expect(trackItem?.explicit, isA());
+ expect(
+ trackItem?.externalUrls,
+ isA(),
+ );
+ expect(
+ trackItem?.externalUrls?.externalUrlObject,
+ isA(),
+ );
+ expect(
+ trackItem?.externalUrls?.externalUrlObject.spotify,
+ isA(),
+ );
+ expect(trackItem?.href, isA());
+ expect(trackItem?.id, isA());
+ expect(trackItem?.isPlayable, isA());
+ expect(trackItem?.linkedFrom, isA());
+ expect(
+ trackItem?.linkedFrom?.linkedTrackObject.externalUrls,
+ isA(),
+ );
+ expect(trackItem?.linkedFrom?.linkedTrackObject.href, isA());
+ expect(trackItem?.linkedFrom?.linkedTrackObject.id, isA());
+ expect(trackItem?.linkedFrom?.linkedTrackObject.$type, isA());
+ expect(trackItem?.linkedFrom?.linkedTrackObject.uri, isA());
+ expect(
+ trackItem?.restrictions,
+ isA(),
+ );
+ expect(
+ trackItem?.restrictions?.trackRestrictionObject.reason,
+ isA(),
+ );
+ expect(trackItem?.name, isA());
+ expect(trackItem?.previewUrl, isA());
+ expect(trackItem?.trackNumber, isA());
+ expect(trackItem?.$type, isA());
+ expect(trackItem?.uri, isA());
+ expect(trackItem?.isLocal, isA());
+
+ expect(albumObject.copyrights, isA?>());
+ final copyright = albumObject.copyrights?.first;
+ expect(copyright?.text, isA());
+ expect(copyright?.$type, isA());
+
+ expect(albumObject.externalIds, isA());
+ expect(albumObject.genres, isA?>());
+ expect(albumObject.label, isA());
+ expect(albumObject.popularity, isA());
+ });
+ });
+}
diff --git a/integration_test/music_streaming/music_streaming_test/test/test_helper.dart b/integration_test/music_streaming/music_streaming_test/test/test_helper.dart
new file mode 100644
index 0000000..5af0ef0
--- /dev/null
+++ b/integration_test/music_streaming/music_streaming_test/test/test_helper.dart
@@ -0,0 +1,84 @@
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:path/path.dart' as path;
+import 'package:test/test.dart';
+
+class ImposterServer {
+ ImposterServer({required this.port});
+
+ Process? _process;
+ final int port;
+
+ Future start() async {
+ final imposterJar = path.join(
+ Directory.current.parent.parent.path,
+ 'imposter.jar',
+ );
+
+ if (!File(imposterJar).existsSync()) {
+ throw Exception(
+ 'Imposter JAR not found at $imposterJar. Please download it first.',
+ );
+ }
+
+ _process = await Process.start('java', [
+ '-jar',
+ imposterJar,
+ '--listenPort',
+ port.toString(),
+ '--configDir',
+ path.join(Directory.current.path, 'imposter'),
+ '--plugin',
+ 'openapi',
+ '--plugin',
+ 'rest',
+ ],
+ environment: {
+ ...Platform.environment,
+ 'IMPOSTER_LOG_LEVEL': 'DEBUG',
+ });
+
+ _process!.stdout.transform(const Utf8Decoder()).listen((data) {
+ print('Imposter stdout: $data');
+ });
+ _process!.stderr.transform(const Utf8Decoder()).listen((data) {
+ print('Imposter stderr: $data');
+ });
+
+ await _waitForImposterReady();
+ }
+
+ Future _waitForImposterReady({int timeoutSec = 10}) async {
+ final deadline = DateTime.now().add(Duration(seconds: timeoutSec));
+ final client = HttpClient();
+
+ while (DateTime.now().isBefore(deadline)) {
+ try {
+ final request = await client.getUrl(
+ Uri.parse('http://localhost:$port'),
+ );
+ await request.close();
+
+ return true; // No exception means the server is ready.
+ } on SocketException catch (_) {
+ // ignore
+ }
+ await Future.delayed(const Duration(milliseconds: 300));
+ }
+ return false;
+ }
+
+ Future stop() async {
+ if (_process != null) {
+ _process!.kill();
+ await _process!.exitCode;
+ _process = null;
+ }
+ }
+}
+
+Future setupImposterServer(ImposterServer server) async {
+ await server.start();
+ addTearDown(() => server.stop());
+}
From 96e85d08d837871e3161044e9deb25cc2b5e5790 Mon Sep 17 00:00:00 2001
From: Tobias Ottenweller
Date: Mon, 9 Jun 2025 12:54:10 +0200
Subject: [PATCH 5/5] test: add get album error response tests
---
.../imposter/imposter-config.json | 5 +-
.../imposter/response.groovy | 7 +++
.../test/albums_test.dart | 51 +++++++++++++++++++
3 files changed, 62 insertions(+), 1 deletion(-)
create mode 100644 integration_test/music_streaming/music_streaming_test/imposter/response.groovy
diff --git a/integration_test/music_streaming/music_streaming_test/imposter/imposter-config.json b/integration_test/music_streaming/music_streaming_test/imposter/imposter-config.json
index ad98044..a2480b3 100644
--- a/integration_test/music_streaming/music_streaming_test/imposter/imposter-config.json
+++ b/integration_test/music_streaming/music_streaming_test/imposter/imposter-config.json
@@ -1,4 +1,7 @@
{
"plugin": "openapi",
- "specFile": "../../openapi.yaml"
+ "specFile": "../../openapi.yaml",
+ "response": {
+ "scriptFile": "response.groovy"
+ }
}
\ No newline at end of file
diff --git a/integration_test/music_streaming/music_streaming_test/imposter/response.groovy b/integration_test/music_streaming/music_streaming_test/imposter/response.groovy
new file mode 100644
index 0000000..65422ea
--- /dev/null
+++ b/integration_test/music_streaming/music_streaming_test/imposter/response.groovy
@@ -0,0 +1,7 @@
+// Get the response status from the request header
+def responseStatus = context.request.headers['X-Response-Status'] ?: '200'
+
+// Set the response status code and use the OpenAPI specification
+respond()
+ .withStatusCode(Integer.parseInt(responseStatus))
+ .usingDefaultBehaviour()
diff --git a/integration_test/music_streaming/music_streaming_test/test/albums_test.dart b/integration_test/music_streaming/music_streaming_test/test/albums_test.dart
index 190105f..d410e37 100644
--- a/integration_test/music_streaming/music_streaming_test/test/albums_test.dart
+++ b/integration_test/music_streaming/music_streaming_test/test/albums_test.dart
@@ -189,5 +189,56 @@ void main() {
expect(albumObject.label, isA());
expect(albumObject.popularity, isA());
});
+
+ test('401', () async {
+ final albumsApi = buildAlbumsApi(responseStatus: '401');
+
+ final response = await albumsApi.getAnAlbum(id: 'abc', market: 'en');
+
+ expect(response, isA>());
+ final success = response as TonikSuccess;
+ expect(success.response.statusCode, 401);
+ expect(success.value, isA());
+
+ final value = success.value as GetAnAlbumResponse401;
+ expect(value.body, isA());
+ expect(value.body.error, isA());
+ expect(value.body.error.status, isA());
+ expect(value.body.error.message, isA());
+ });
+
+ test('403', () async {
+ final albumsApi = buildAlbumsApi(responseStatus: '403');
+
+ final response = await albumsApi.getAnAlbum(id: 'abc', market: 'en');
+
+ expect(response, isA>());
+ final success = response as TonikSuccess;
+ expect(success.response.statusCode, 403);
+ expect(success.value, isA());
+
+ final value = success.value as GetAnAlbumResponse403;
+ expect(value.body, isA());
+ expect(value.body.error, isA());
+ expect(value.body.error.status, isA());
+ expect(value.body.error.message, isA());
+ });
+
+ test('429', () async {
+ final albumsApi = buildAlbumsApi(responseStatus: '429');
+
+ final response = await albumsApi.getAnAlbum(id: 'abc', market: 'en');
+
+ expect(response, isA>());
+ final success = response as TonikSuccess;
+ expect(success.response.statusCode, 429);
+ expect(success.value, isA());
+
+ final value = success.value as GetAnAlbumResponse429;
+ expect(value.body, isA());
+ expect(value.body.error, isA());
+ expect(value.body.error.status, isA());
+ expect(value.body.error.message, isA());
+ });
});
}