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/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..a2480b3 --- /dev/null +++ b/integration_test/music_streaming/music_streaming_test/imposter/imposter-config.json @@ -0,0 +1,7 @@ +{ + "plugin": "openapi", + "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/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..d410e37 --- /dev/null +++ b/integration_test/music_streaming/music_streaming_test/test/albums_test.dart @@ -0,0 +1,244 @@ +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()); + }); + + 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()); + }); + }); +} 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()); +} 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 @@ stars on github tests +

- - # Tonik A Dart code generator for OpenAPI 3.0 and 3.1 specifications. diff --git a/packages/tonik_generate/lib/src/naming/name_manager.dart b/packages/tonik_generate/lib/src/naming/name_manager.dart index aadbe2d..a3c7671 100644 --- a/packages/tonik_generate/lib/src/naming/name_manager.dart +++ b/packages/tonik_generate/lib/src/naming/name_manager.dart @@ -58,7 +58,16 @@ class NameManager { _logServerName(entry.value, entry.key); } - for (final model in models) { + for (final model in models.where( + (m) => m is NamedModel && m.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); } 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..2eb923e 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,70 @@ void main() { expect(baseName2, baseName); expect(identical(implementationNames, implementationNames2), isTrue); }); + + group('model naming behavior', () { + late Context userContext; + + setUp(() { + userContext = Context.initial().pushAll([ + 'components', + 'schemas', + 'user', + ]); + }); + + 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 +690,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 +701,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 +738,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); });