| // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import 'dart:io'; | |
| import 'package:json_annotation/json_annotation.dart'; | |
| import 'package:pub_semver/pub_semver.dart'; | |
| import 'package:pubspec_parse/pubspec_parse.dart'; | |
| import 'package:test/test.dart'; | |
| import 'test_utils.dart'; | |
| /// The pub client only accepts `tag_pattern` from Dart 3.9, so the | |
| /// cross-check against it is skipped on older SDKs. | |
| final _sdkSupportsTagPattern = | |
| Version.parse(Platform.version.split(' ').first) >= Version(3, 9, 0); | |
| void main() { | |
| group('hosted', _hostedDependency); | |
| group('git', _gitDependency); | |
| group('sdk', _sdkDependency); | |
| group('path', _pathDependency); | |
| group('errors', () { | |
| test('List', () { | |
| _expectThrows([], r''' | |
| line 4, column 10: Unsupported value for "dep". Not a valid dependency value. | |
| ╷ | |
| 4 │ "dep": [] | |
| │ ^^ | |
| ╵'''); | |
| }); | |
| test('int', () { | |
| _expectThrows(42, r''' | |
| line 4, column 10: Unsupported value for "dep". Not a valid dependency value. | |
| ╷ | |
| 4 │ "dep": 42 | |
| │ ┌──────────^ | |
| 5 │ │ } | |
| │ └─^ | |
| ╵'''); | |
| }); | |
| test('map with too many keys', () { | |
| _expectThrows( | |
| {'path': 'a', 'git': 'b'}, | |
| r''' | |
| line 6, column 11: Unsupported value for "git". A dependency may only have one source. | |
| ╷ | |
| 6 │ "git": "b" | |
| │ ^^^ | |
| ╵''', | |
| ); | |
| }); | |
| test('map with unsupported keys', () { | |
| _expectThrows( | |
| {'bob': 'a', 'jones': 'b'}, | |
| r''' | |
| line 5, column 4: Unrecognized keys: [bob]; supported keys: [sdk, git, path, hosted] | |
| ╷ | |
| 5 │ "bob": "a", | |
| │ ^^^^^ | |
| ╵''', | |
| ); | |
| }); | |
| test('plain Map in Pubspec.fromJson preserves nested key and map', () { | |
| final gitMap = {'url': 42}; | |
| expect( | |
| () => Pubspec.fromJson({ | |
| 'name': 'sample', | |
| 'dependencies': { | |
| 'dep': {'git': gitMap}, | |
| }, | |
| }), | |
| throwsA( | |
| isA<CheckedFromJsonException>() | |
| .having((e) => e.key, 'key', 'url') | |
| .having((e) => e.map, 'map', same(gitMap)), | |
| ), | |
| ); | |
| }); | |
| }); | |
| } | |
| void _hostedDependency() { | |
| test('null', () async { | |
| final dep = await _dependency<HostedDependency>(null); | |
| expect(dep.version.toString(), 'any'); | |
| expect(dep.hosted, isNull); | |
| expect(dep.toString(), 'HostedDependency: any'); | |
| }); | |
| test('empty map', () async { | |
| final dep = await _dependency<HostedDependency>({}); | |
| expect(dep.hosted, isNull); | |
| expect(dep.toString(), 'HostedDependency: any'); | |
| }); | |
| test('string version', () async { | |
| final dep = await _dependency<HostedDependency>('^1.0.0'); | |
| expect(dep.version.toString(), '^1.0.0'); | |
| expect(dep.hosted, isNull); | |
| expect(dep.toString(), 'HostedDependency: ^1.0.0'); | |
| }); | |
| test('bad string version', () { | |
| _expectThrows('not a version', r''' | |
| line 4, column 10: Unsupported value for "dep". Could not parse version "not a version". Unknown text at "not a version". | |
| ╷ | |
| 4 │ "dep": "not a version" | |
| │ ^^^^^^^^^^^^^^^ | |
| ╵'''); | |
| }); | |
| test('map w/ just version', () async { | |
| final dep = await _dependency<HostedDependency>({'version': '^1.0.0'}); | |
| expect(dep.version.toString(), '^1.0.0'); | |
| expect(dep.hosted, isNull); | |
| expect(dep.toString(), 'HostedDependency: ^1.0.0'); | |
| }); | |
| test('map w/ version and hosted as Map', () async { | |
| final dep = await _dependency<HostedDependency>({ | |
| 'version': '^1.0.0', | |
| 'hosted': {'name': 'hosted_name', 'url': 'https://hosted_url'}, | |
| }); | |
| expect(dep.version.toString(), '^1.0.0'); | |
| expect(dep.hosted!.name, 'hosted_name'); | |
| expect(dep.hosted!.url.toString(), 'https://hosted_url'); | |
| expect(dep.toString(), 'HostedDependency: ^1.0.0'); | |
| }); | |
| test('map /w hosted as a map without name', () async { | |
| final dep = await _dependency<HostedDependency>( | |
| { | |
| 'version': '^1.0.0', | |
| 'hosted': {'url': 'https://hosted_url'}, | |
| }, | |
| skipTryPub: true, // todo: Unskip once pub supports this syntax | |
| ); | |
| expect(dep.version.toString(), '^1.0.0'); | |
| expect(dep.hosted!.declaredName, isNull); | |
| expect(dep.hosted!.name, 'dep'); | |
| expect(dep.hosted!.url.toString(), 'https://hosted_url'); | |
| expect(dep.toString(), 'HostedDependency: ^1.0.0'); | |
| }); | |
| test('map w/ hosted url parsed as non-git URI', () async { | |
| final dep = await _dependency<HostedDependency>({ | |
| 'version': '^1.0.0', | |
| 'hosted': {'name': 'hosted_name', 'url': 'pub.example.com:8080'}, | |
| }, skipTryPub: true); | |
| expect(dep.hosted!.url, Uri.parse('pub.example.com:8080')); | |
| expect(dep.hosted!.url!.scheme, 'pub.example.com'); | |
| }); | |
| test('map w/ bad version value', () { | |
| _expectThrows( | |
| { | |
| 'version': 'not a version', | |
| 'hosted': {'name': 'hosted_name', 'url': 'hosted_url'}, | |
| }, | |
| r''' | |
| line 5, column 15: Unsupported value for "version". Could not parse version "not a version". Unknown text at "not a version". | |
| ╷ | |
| 5 │ "version": "not a version", | |
| │ ^^^^^^^^^^^^^^^ | |
| ╵''', | |
| ); | |
| }); | |
| test('map w/ extra keys should fail', () { | |
| _expectThrows( | |
| { | |
| 'version': '^1.0.0', | |
| 'hosted': {'name': 'hosted_name', 'url': 'hosted_url'}, | |
| 'not_supported': null, | |
| }, | |
| r''' | |
| line 10, column 4: Unrecognized keys: [not_supported]; supported keys: [sdk, git, path, hosted] | |
| ╷ | |
| 10 │ "not_supported": null | |
| │ ^^^^^^^^^^^^^^^ | |
| ╵''', | |
| ); | |
| }); | |
| test('map w/ version and hosted as String', () async { | |
| final dep = await _dependency<HostedDependency>( | |
| {'version': '^1.0.0', 'hosted': 'hosted_url'}, | |
| skipTryPub: true, // todo: Unskip once put supports this | |
| ); | |
| expect(dep.version.toString(), '^1.0.0'); | |
| expect(dep.hosted!.declaredName, isNull); | |
| expect(dep.hosted!.name, 'dep'); | |
| expect(dep.hosted!.url, Uri.parse('hosted_url')); | |
| expect(dep.toString(), 'HostedDependency: ^1.0.0'); | |
| }); | |
| test('map w/ hosted as String', () async { | |
| final dep = await _dependency<HostedDependency>({'hosted': 'hosted_url'}); | |
| expect(dep.version, VersionConstraint.any); | |
| expect(dep.hosted!.declaredName, isNull); | |
| expect(dep.hosted!.name, 'dep'); | |
| expect(dep.hosted!.url, Uri.parse('hosted_url')); | |
| expect(dep.toString(), 'HostedDependency: any'); | |
| }); | |
| test('map w/ null hosted should error', () { | |
| _expectThrows( | |
| {'hosted': null}, | |
| r''' | |
| line 5, column 4: These keys had `null` values, which is not allowed: [hosted] | |
| ╷ | |
| 5 │ "hosted": null | |
| │ ^^^^^^^^ | |
| ╵''', | |
| ); | |
| }); | |
| test('map w/ invalid hosted String should error on hosted key', () { | |
| _expectThrows( | |
| {'hosted': '://bad_uri'}, | |
| r''' | |
| line 5, column 14: Unsupported value for "hosted". Invalid empty scheme at offset 0. | |
| ╷ | |
| 5 │ "hosted": "://bad_uri" | |
| │ ^^^^^^^^^^^^ | |
| ╵''', | |
| ); | |
| }); | |
| test('map w/ null version is fine', () async { | |
| final dep = await _dependency<HostedDependency>({'version': null}); | |
| expect(dep.version, VersionConstraint.any); | |
| expect(dep.hosted, isNull); | |
| expect(dep.toString(), 'HostedDependency: any'); | |
| }); | |
| } | |
| void _sdkDependency() { | |
| test('without version', () async { | |
| final dep = await _dependency<SdkDependency>({'sdk': 'flutter'}); | |
| expect(dep.sdk, 'flutter'); | |
| expect(dep.version, VersionConstraint.any); | |
| expect(dep.toString(), 'SdkDependency: flutter'); | |
| }); | |
| test('with version', () async { | |
| final dep = await _dependency<SdkDependency>({ | |
| 'sdk': 'flutter', | |
| 'version': '>=1.2.3 <2.0.0', | |
| }); | |
| expect(dep.sdk, 'flutter'); | |
| expect(dep.version.toString(), '>=1.2.3 <2.0.0'); | |
| expect(dep.toString(), 'SdkDependency: flutter'); | |
| }); | |
| test('null content', () { | |
| _expectThrowsContaining({ | |
| 'sdk': null, | |
| }, r"type 'Null' is not a subtype of type 'String'"); | |
| }); | |
| test('number content', () { | |
| _expectThrowsContaining({ | |
| 'sdk': 42, | |
| }, r"type 'int' is not a subtype of type 'String'"); | |
| }); | |
| } | |
| void _gitDependency() { | |
| test('string', () async { | |
| final dep = await _dependency<GitDependency>({'git': 'url'}); | |
| expect(dep.url.toString(), 'url'); | |
| expect(dep.path, isNull); | |
| expect(dep.ref, isNull); | |
| expect(dep.toString(), 'GitDependency: url@url'); | |
| }); | |
| test('string with version key', () async { | |
| // Regression test for https://github.com/dart-lang/pubspec_parse/issues/13 | |
| final dep = await _dependency<GitDependency>({ | |
| 'git': 'url', | |
| 'version': '^1.2.3', | |
| }); | |
| expect(dep.url.toString(), 'url'); | |
| expect(dep.path, isNull); | |
| expect(dep.ref, isNull); | |
| expect(dep.tagPattern, isNull); | |
| expect(dep.version, VersionConstraint.parse('^1.2.3')); | |
| expect(dep.toString(), 'GitDependency: url@url'); | |
| }); | |
| test('string without version key', () async { | |
| final dep = await _dependency<GitDependency>({'git': 'url'}); | |
| expect(dep.version, isNull); | |
| }); | |
| test('string with invalid version key fails', () { | |
| _expectThrows( | |
| {'git': 'url', 'version': 'not a version'}, | |
| r''' | |
| line 6, column 15: Unsupported value for "version". Could not parse version "not a version". Unknown text at "not a version". | |
| ╷ | |
| 6 │ "version": "not a version" | |
| │ ^^^^^^^^^^^^^^^ | |
| ╵''', | |
| ); | |
| }); | |
| test('string with non-String version key fails', () { | |
| _expectThrows( | |
| {'git': 'url', 'version': 42}, | |
| r''' | |
| line 6, column 15: Unsupported value for "version". `42` is not a String. | |
| ╷ | |
| 6 │ "version": 42 | |
| │ ┌───────────────^ | |
| 7 │ │ } | |
| │ └──^ | |
| ╵''', | |
| ); | |
| }); | |
| test('string with user@ URL', () async { | |
| final skipTryParse = Platform.environment.containsKey('TRAVIS'); | |
| if (skipTryParse) { | |
| print('FYI: not validating git@ URI on travis due to failure'); | |
| } | |
| final dep = await _dependency<GitDependency>({ | |
| 'git': 'git@localhost:dep.git', | |
| }, skipTryPub: skipTryParse); | |
| expect(dep.url.toString(), 'ssh://git@localhost/dep.git'); | |
| expect(dep.path, isNull); | |
| expect(dep.ref, isNull); | |
| expect(dep.toString(), 'GitDependency: url@ssh://git@localhost/dep.git'); | |
| }); | |
| test('string with random extra key fails', () { | |
| _expectThrows( | |
| {'git': 'url', 'bob': '^1.2.3'}, | |
| r''' | |
| line 6, column 4: Unrecognized keys: [bob]; supported keys: [sdk, git, path, hosted] | |
| ╷ | |
| 6 │ "bob": "^1.2.3" | |
| │ ^^^^^ | |
| ╵''', | |
| ); | |
| }); | |
| test('map', () async { | |
| final dep = await _dependency<GitDependency>({ | |
| 'git': {'url': 'url', 'path': 'path', 'ref': 'ref'}, | |
| }); | |
| expect(dep.url.toString(), 'url'); | |
| expect(dep.path, 'path'); | |
| expect(dep.ref, 'ref'); | |
| expect(dep.tagPattern, isNull); | |
| expect(dep.version, isNull); | |
| expect(dep.toString(), 'GitDependency: url@url'); | |
| }); | |
| test('map with tag_pattern and version', () async { | |
| final dep = await _dependency<GitDependency>( | |
| { | |
| 'git': {'url': 'url', 'path': 'path', 'tag_pattern': 'v{{version}}'}, | |
| 'version': '^1.2.3', | |
| }, | |
| sdkConstraint: '^3.9.0', | |
| skipTryPub: !_sdkSupportsTagPattern, | |
| ); | |
| expect(dep.url.toString(), 'url'); | |
| expect(dep.path, 'path'); | |
| expect(dep.ref, isNull); | |
| expect(dep.tagPattern, 'v{{version}}'); | |
| expect(dep.version, VersionConstraint.parse('^1.2.3')); | |
| expect(dep.toString(), 'GitDependency: url@url'); | |
| }); | |
| test('map with tag_pattern without version', () async { | |
| final dep = await _dependency<GitDependency>( | |
| { | |
| 'git': {'url': 'url', 'tag_pattern': 'v{{version}}'}, | |
| }, | |
| sdkConstraint: '^3.9.0', | |
| skipTryPub: !_sdkSupportsTagPattern, | |
| ); | |
| expect(dep.tagPattern, 'v{{version}}'); | |
| expect(dep.version, isNull); | |
| }); | |
| test('map with tag_pattern and ref fails', () { | |
| _expectThrows( | |
| { | |
| 'git': {'url': 'url', 'ref': 'ref', 'tag_pattern': 'v{{version}}'}, | |
| }, | |
| r''' | |
| line 11, column 20: Unsupported value for "tag_pattern". Cannot be used together with "ref". | |
| ╷ | |
| 11 │ "tag_pattern": "v{{version}}" | |
| │ ^^^^^^^^^^^^^^ | |
| ╵''', | |
| sdkConstraint: '^3.9.0', | |
| ); | |
| }); | |
| test('map with tag_pattern missing {{version}} fails', () { | |
| _expectThrows( | |
| { | |
| 'git': {'url': 'url', 'tag_pattern': 'v'}, | |
| }, | |
| r''' | |
| line 10, column 20: Unsupported value for "tag_pattern". Must contain a single "{{version}}". | |
| ╷ | |
| 10 │ "tag_pattern": "v" | |
| │ ^^^ | |
| ╵''', | |
| sdkConstraint: '^3.9.0', | |
| ); | |
| }); | |
| test('map with tag_pattern containing {{version}} twice fails', () { | |
| _expectThrows( | |
| { | |
| 'git': {'url': 'url', 'tag_pattern': '{{version}}-{{version}}'}, | |
| }, | |
| r''' | |
| line 10, column 20: Unsupported value for "tag_pattern". Must contain a single "{{version}}". | |
| ╷ | |
| 10 │ "tag_pattern": "{{version}}-{{version}}" | |
| │ ^^^^^^^^^^^^^^^^^^^^^^^^^ | |
| ╵''', | |
| sdkConstraint: '^3.9.0', | |
| ); | |
| }); | |
| test('map with non-String tag_pattern fails', () { | |
| _expectThrowsContaining( | |
| { | |
| 'git': {'url': 'url', 'tag_pattern': 42}, | |
| }, | |
| 'Unsupported value for "tag_pattern". ' | |
| "type 'int' is not a subtype of type 'String?' in type cast", | |
| sdkConstraint: '^3.9.0', | |
| ); | |
| }); | |
| test('git - null content', () { | |
| _expectThrows( | |
| {'git': null}, | |
| r''' | |
| line 5, column 11: Unsupported value for "git". Must be a String or a Map. | |
| ╷ | |
| 5 │ "git": null | |
| │ ┌───────────^ | |
| 6 │ │ } | |
| │ └──^ | |
| ╵''', | |
| ); | |
| }); | |
| test('git - int content', () { | |
| _expectThrows( | |
| {'git': 42}, | |
| r''' | |
| line 5, column 11: Unsupported value for "git". Must be a String or a Map. | |
| ╷ | |
| 5 │ "git": 42 | |
| │ ┌───────────^ | |
| 6 │ │ } | |
| │ └──^ | |
| ╵''', | |
| ); | |
| }); | |
| test('git - empty map', () { | |
| _expectThrowsContaining({ | |
| 'git': <String, dynamic>{}, | |
| }, r"type 'Null' is not a subtype of type 'String'"); | |
| }); | |
| test('git - null url', () { | |
| _expectThrowsContaining({ | |
| 'git': {'url': null}, | |
| }, r"type 'Null' is not a subtype of type 'String'"); | |
| }); | |
| test('git - int url', () { | |
| _expectThrowsContaining({ | |
| 'git': {'url': 42}, | |
| }, r"type 'int' is not a subtype of type 'String'"); | |
| }); | |
| test('git - invalid string url', () { | |
| _expectThrows( | |
| {'git': '://bad_uri'}, | |
| r''' | |
| line 5, column 11: Unsupported value for "git". Invalid empty scheme | |
| ╷ | |
| 5 │ "git": "://bad_uri" | |
| │ ^^^^^^^^^^^^ | |
| ╵''', | |
| ); | |
| }); | |
| } | |
| void _pathDependency() { | |
| test('valid', () async { | |
| final dep = await _dependency<PathDependency>({'path': '../path'}); | |
| expect(dep.path, '../path'); | |
| expect(dep.toString(), 'PathDependency: path@../path'); | |
| }); | |
| test('valid with version key exposes the constraint', () async { | |
| final dep = await _dependency<PathDependency>({ | |
| 'path': '../path', | |
| 'version': '^1.2.3', | |
| }); | |
| expect(dep.path, '../path'); | |
| expect(dep.version, VersionConstraint.parse('^1.2.3')); | |
| expect(dep.toString(), 'PathDependency: path@../path'); | |
| }); | |
| test('without a version key defaults to null', () async { | |
| final dep = await _dependency<PathDependency>({'path': '../path'}); | |
| expect(dep.version, isNull); | |
| }); | |
| test('invalid version key fails', () { | |
| _expectThrows( | |
| {'path': '../path', 'version': 'not a version'}, | |
| r''' | |
| line 6, column 15: Unsupported value for "version". Could not parse version "not a version". Unknown text at "not a version". | |
| ╷ | |
| 6 │ "version": "not a version" | |
| │ ^^^^^^^^^^^^^^^ | |
| ╵''', | |
| ); | |
| }); | |
| test('non-String version key fails', () { | |
| _expectThrows( | |
| {'path': '../path', 'version': 42}, | |
| r''' | |
| line 6, column 15: Unsupported value for "version". `42` is not a String. | |
| ╷ | |
| 6 │ "version": 42 | |
| │ ┌───────────────^ | |
| 7 │ │ } | |
| │ └──^ | |
| ╵''', | |
| ); | |
| }); | |
| test('valid with random extra key fails', () { | |
| _expectThrows( | |
| {'path': '../path', 'bob': '^1.2.3'}, | |
| r''' | |
| line 6, column 4: Unrecognized keys: [bob]; supported keys: [sdk, git, path, hosted] | |
| ╷ | |
| 6 │ "bob": "^1.2.3" | |
| │ ^^^^^ | |
| ╵''', | |
| ); | |
| }); | |
| test('null content', () { | |
| _expectThrows( | |
| {'path': null}, | |
| r''' | |
| line 5, column 12: Unsupported value for "path". Must be a String. | |
| ╷ | |
| 5 │ "path": null | |
| │ ┌────────────^ | |
| 6 │ │ } | |
| │ └──^ | |
| ╵''', | |
| ); | |
| }); | |
| test('int content', () { | |
| _expectThrows( | |
| {'path': 42}, | |
| r''' | |
| line 5, column 12: Unsupported value for "path". Must be a String. | |
| ╷ | |
| 5 │ "path": 42 | |
| │ ┌────────────^ | |
| 6 │ │ } | |
| │ └──^ | |
| ╵''', | |
| ); | |
| }); | |
| } | |
| void _expectThrows( | |
| Object content, | |
| String expectedError, { | |
| String? sdkConstraint, | |
| }) { | |
| expectParseThrows({ | |
| 'name': 'sample', | |
| if (sdkConstraint != null) 'environment': {'sdk': sdkConstraint}, | |
| 'dependencies': {'dep': content}, | |
| }, expectedError); | |
| } | |
| void _expectThrowsContaining( | |
| Object content, | |
| String errorText, { | |
| String? sdkConstraint, | |
| }) { | |
| expectParseThrowsContaining({ | |
| 'name': 'sample', | |
| if (sdkConstraint != null) 'environment': {'sdk': sdkConstraint}, | |
| 'dependencies': {'dep': content}, | |
| }, errorText); | |
| } | |
| Future<T> _dependency<T extends Dependency>( | |
| Object? content, { | |
| bool skipTryPub = false, | |
| String? sdkConstraint, | |
| }) async { | |
| final value = await parse({ | |
| ...defaultPubspec, | |
| if (sdkConstraint != null) 'environment': {'sdk': sdkConstraint}, | |
| 'dependencies': {'dep': content}, | |
| }, skipTryPub: skipTryPub); | |
| expect(value.name, 'sample'); | |
| expect(value.dependencies, hasLength(1)); | |
| final entry = value.dependencies.entries.single; | |
| expect(entry.key, 'dep'); | |
| return entry.value as T; | |
| } |