From 0ccca8b0ce82853ead0592529d4ae42064346996 Mon Sep 17 00:00:00 2001 From: gibahjoe Date: Tue, 16 Jan 2024 02:16:21 +0100 Subject: [PATCH] Revert "Refactor testing" --- .github/workflows/code_quality.yml | 9 +- example/pubspec.yaml | 6 +- .../openapi_generator_annotations_base.dart | 14 +- openapi-generator-annotations/pubspec.yaml | 8 - .../openapi_generator_annotations_test.dart | 3 +- openapi-generator/.gitignore | 4 +- openapi-generator/Makefile | 4 - openapi-generator/docker-compose.yaml | 13 + .../lib/src/extensions/type_methods.dart | 99 ++ .../lib/src/gen_on_spec_changes.dart | 34 +- openapi-generator/lib/src/models/command.dart | 53 - .../lib/src/models/generator_arguments.dart | 99 +- .../lib/src/openapi_generator_runner.dart | 298 +++-- openapi-generator/lib/src/utils.dart | 2 +- openapi-generator/pubspec.yaml | 12 +- openapi-generator/test/builder_test.dart | 588 +++++++++ openapi-generator/test/command_test.dart | 80 -- .../test/generator_arguments_test.dart | 219 +++- openapi-generator/test/generator_test.dart | 1100 ----------------- openapi-generator/test/mocks.dart | 21 - openapi-generator/test/specs/buckets.sh | 6 + .../next_gen_builder_flutter_test_config.dart | 2 +- ...next_gen_builder_flutterw_test_config.dart | 17 - ... => next_gen_builder_test_aws_config.dart} | 12 +- .../specs/next_gen_builder_test_config.dart | 2 +- .../next_gen_builder_test_invalid_config.dart | 16 - .../test/test_annotations/test_configs.dart | 77 ++ .../test/test_annotations/test_generator.dart | 113 ++ openapi-generator/test/utils.dart | 55 +- .../test/verify_generation_test.dart | 17 + 30 files changed, 1378 insertions(+), 1605 deletions(-) delete mode 100644 openapi-generator/Makefile create mode 100644 openapi-generator/docker-compose.yaml create mode 100644 openapi-generator/test/builder_test.dart delete mode 100644 openapi-generator/test/generator_test.dart delete mode 100644 openapi-generator/test/mocks.dart create mode 100755 openapi-generator/test/specs/buckets.sh delete mode 100644 openapi-generator/test/specs/next_gen_builder_flutterw_test_config.dart rename openapi-generator/test/specs/{next_gen_builder_fvm_test_config.dart => next_gen_builder_test_aws_config.dart} (61%) delete mode 100644 openapi-generator/test/specs/next_gen_builder_test_invalid_config.dart create mode 100644 openapi-generator/test/test_annotations/test_configs.dart create mode 100644 openapi-generator/test/test_annotations/test_generator.dart create mode 100644 openapi-generator/test/verify_generation_test.dart diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml index 71d16f3..7f165a3 100644 --- a/.github/workflows/code_quality.yml +++ b/.github/workflows/code_quality.yml @@ -25,6 +25,9 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 + - name: Start Docker compose containers + if: ${{ matrix.work_dir == 'openapi-generator' }} + run: docker-compose -f "docker-compose.yaml" up -d --build - name: Setup Dart uses: dart-lang/setup-dart@v1.5.0 with: @@ -33,9 +36,6 @@ jobs: run: dart pub get - name: Validate formatting run: dart format ./ --set-exit-if-changed - - name: Generate Mocks - if: ${{ matrix.work_dir == 'openapi-generator' }} - run: dart run build_runner build --delete-conflicting-outputs - name: Run analyzer run: dart analyze --fatal-warnings - name: Run tests @@ -51,6 +51,9 @@ jobs: verbose: true flags: ${{ matrix.work_dir }} CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + - name: Stop Docker Container + if: ${{ matrix.work_dir == 'openapi-generator' && always() }} + run: docker-compose -f "docker-compose.yaml" down build: name: Build example project 🛠️ diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 2dc40d3..16e3f52 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -27,11 +27,7 @@ dependency_overrides: path: ../openapi-generator openapi_generator_cli: path: ../openapi-generator-cli - source_gen: - git: - ref: reviver - url: https://github.com/Nexushunter/source_gen - path: source_gen + # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions diff --git a/openapi-generator-annotations/lib/src/openapi_generator_annotations_base.dart b/openapi-generator-annotations/lib/src/openapi_generator_annotations_base.dart index 883f766..007f60e 100644 --- a/openapi-generator-annotations/lib/src/openapi_generator_annotations_base.dart +++ b/openapi-generator-annotations/lib/src/openapi_generator_annotations_base.dart @@ -1,11 +1,8 @@ -library openapi_generator_annotations; - import 'dart:convert'; import 'dart:io'; import 'package:crypto/crypto.dart'; import 'package:meta/meta.dart'; -import 'package:source_gen/source_gen.dart'; /// Config base class /// Your annotated class must extend this config class @@ -229,6 +226,11 @@ class RemoteSpec extends InputSpec { const RemoteSpec.empty() : this(path: 'http://localhost:8080/'); Uri get url => Uri.parse(path); + + RemoteSpec.fromMap(Map map) + : headerDelegate = + map['headerDelegate'] ?? const RemoteSpecHeaderDelegate(), + super.fromMap(map); } /// Default [RemoteSpecHeaderDelegate] used when retrieving a remote OAS spec. @@ -264,6 +266,12 @@ class AWSRemoteSpecHeaderDelegate extends RemoteSpecHeaderDelegate { this.accessKeyId = null, }) : super(); + AWSRemoteSpecHeaderDelegate.fromMap(Map map) + : bucket = map['bucket'], + accessKeyId = map['accessKeyId'], + secretAccessKey = map['secretAccessKey'], + super.fromMap(map); + /// Generates the [header] map used within the GET request. /// /// Assumes that the user's auth AWS credentials diff --git a/openapi-generator-annotations/pubspec.yaml b/openapi-generator-annotations/pubspec.yaml index a1a8b8e..289d60f 100644 --- a/openapi-generator-annotations/pubspec.yaml +++ b/openapi-generator-annotations/pubspec.yaml @@ -10,16 +10,8 @@ environment: dependencies: crypto: '>=3.0.0 <4.0.0' meta: '>=1.3.0 <2.0.0' - source_gen: dev_dependencies: test: source_gen_test: lint: - -dependency_overrides: - source_gen: - git: - ref: reviver - url: https://github.com/Nexushunter/source_gen.git - path: source_gen \ No newline at end of file diff --git a/openapi-generator-annotations/test/openapi_generator_annotations_test.dart b/openapi-generator-annotations/test/openapi_generator_annotations_test.dart index 3011960..a8ea25b 100644 --- a/openapi-generator-annotations/test/openapi_generator_annotations_test.dart +++ b/openapi-generator-annotations/test/openapi_generator_annotations_test.dart @@ -91,7 +91,6 @@ void main() { expect(remote.path, 'http://localhost:8080/'); expect(remote.headerDelegate, isA()); }); - test('uses path', () { final remote = RemoteSpec(path: 'https://example.com/path'); expect(remote.path, 'https://example.com/path'); @@ -113,6 +112,7 @@ void main() { accessKeyId: 'test', secretAccessKey: 'test', ); + test('signs the url correctly', () { final now = DateTime.now(); final actual = delegate.authHeaderContent( @@ -176,7 +176,6 @@ void main() { } }); test('uses the provided environment', () async { - print(Directory.current.path); final result = Process.runSync( 'dart', [ diff --git a/openapi-generator/.gitignore b/openapi-generator/.gitignore index 1810161..a1cc285 100755 --- a/openapi-generator/.gitignore +++ b/openapi-generator/.gitignore @@ -169,6 +169,4 @@ example/.dart_tool # Generated test output test/specs/test-cached.json -test/specs/commands-cache.json -test/specs/localstack -test/mocks.mocks.dart \ No newline at end of file +test/specs/localstack/** \ No newline at end of file diff --git a/openapi-generator/Makefile b/openapi-generator/Makefile deleted file mode 100644 index 92e92a1..0000000 --- a/openapi-generator/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -.PHONY: gen - -gen: - dart run build_runner build --delete-conflicting-outputs \ No newline at end of file diff --git a/openapi-generator/docker-compose.yaml b/openapi-generator/docker-compose.yaml new file mode 100644 index 0000000..64f3ca2 --- /dev/null +++ b/openapi-generator/docker-compose.yaml @@ -0,0 +1,13 @@ +version: '3' +services: + local-s3: + container_name: localstack + image: localstack/localstack:latest + ports: + - "4566:4566" + environment: + - SERVICES=S3 + - AWS_DEFAULT_REGION=us-east-1 + volumes: + - './test/specs/localstack:/var/lib/localstack' + - './test/specs/buckets.sh:/etc/localstack/init/ready.d/buckets.sh' diff --git a/openapi-generator/lib/src/extensions/type_methods.dart b/openapi-generator/lib/src/extensions/type_methods.dart index 72a7160..6f0f422 100644 --- a/openapi-generator/lib/src/extensions/type_methods.dart +++ b/openapi-generator/lib/src/extensions/type_methods.dart @@ -1,6 +1,8 @@ import 'dart:mirrors'; import 'package:analyzer/dart/element/type.dart'; +import 'package:openapi_generator/src/utils.dart'; +import 'package:openapi_generator_annotations/openapi_generator_annotations.dart'; import 'package:source_gen/source_gen.dart' show ConstantReader, TypeChecker; /// Extension adding the type methods to `ConstantReader`. @@ -71,3 +73,100 @@ extension TypeMethods on ConstantReader { return values[enumIndex]; } } + +extension ReadProperty on ConstantReader { + T readPropertyOrDefault(String name, T defaultValue) { + final v = peek(name); + if (v == null) { + return defaultValue; + } + + if (isA(v, InputSpec)) { + final revived = v.revive(); + + if (isA(v, RemoteSpec)) { + final map = revived.namedArguments; + final delegate = map['headerDelegate']; + final mapped = { + 'path': convertToPropertyValue(map['path']!), + }; + if (delegate?.isNull ?? true) { + return RemoteSpec.fromMap(mapped) as T; + } else { + final delegateReader = ConstantReader(delegate); + if (isA(delegateReader, AWSRemoteSpecHeaderDelegate)) { + mapped['headerDelegate'] = AWSRemoteSpecHeaderDelegate.fromMap( + delegateReader.revive().namedArguments.map( + (key, value) => MapEntry( + key, + convertToPropertyValue(value), + ), + ), + ); + } + return RemoteSpec.fromMap(mapped) as T; + } + } else { + final map = revived.namedArguments.map( + (key, value) => MapEntry( + key, + convertToPropertyValue(value), + ), + ); + return InputSpec.fromMap(map) as T; + } + } + + if (isA(v, AdditionalProperties)) { + final map = v.revive().namedArguments.map( + (key, value) => MapEntry( + key, + convertToPropertyValue(value), + ), + ); + if (isA(v, DioProperties)) { + return DioProperties.fromMap(map) as T; + } else if (isA(v, DioAltProperties)) { + return DioAltProperties.fromMap(map) as T; + } else { + return AdditionalProperties.fromMap(map) as T; + } + } + + if (isA(v, InlineSchemaOptions)) { + return InlineSchemaOptions.fromMap( + v.revive().namedArguments.map( + (key, value) => MapEntry( + key, + convertToPropertyValue(value), + ), + ), + ) as T; + } + + if (isA(v, Map)) { + return v.mapValue.map((key, value) => MapEntry( + convertToPropertyValue(key!) as String, + convertToPropertyValue(value!) as String)) as T; + } else if (isA(v, bool)) { + return v.boolValue as T; + } else if (isA(v, double)) { + return v.doubleValue as T; + } else if (isA(v, int)) { + return v.intValue as T; + } else if (isA(v, String)) { + return v.stringValue as T; + } else if (isA(v, Set)) { + return v.setValue.map(convertToPropertyValue) as T; + } else if (isA(v, List)) { + return v.listValue.map(convertToPropertyValue) as T; + } else if (isA(v, Enum)) { + return v.enumValue(); + } else { + return defaultValue; + } + } +} + +bool isA(ConstantReader? v, Type t) => + v?.instanceOf(TypeChecker.fromRuntime(t)) ?? false; diff --git a/openapi-generator/lib/src/gen_on_spec_changes.dart b/openapi-generator/lib/src/gen_on_spec_changes.dart index 176c6de..e221305 100644 --- a/openapi-generator/lib/src/gen_on_spec_changes.dart +++ b/openapi-generator/lib/src/gen_on_spec_changes.dart @@ -59,23 +59,23 @@ FutureOr> loadSpec( } } else { Map? headers; - // if (specConfig.headerDelegate is AWSRemoteSpecHeaderDelegate) { - // try { - // headers = (specConfig.headerDelegate as AWSRemoteSpecHeaderDelegate) - // .header(path: specConfig.url.path); - // } catch (e, st) { - // return Future.error( - // OutputMessage( - // message: 'failed to generate AWS headers', - // additionalContext: e, - // stackTrace: st, - // level: Level.SEVERE, - // ), - // ); - // } - // } else { - // headers = specConfig.headerDelegate.header(); - // } + if (specConfig.headerDelegate is AWSRemoteSpecHeaderDelegate) { + try { + headers = (specConfig.headerDelegate as AWSRemoteSpecHeaderDelegate) + .header(path: specConfig.url.path); + } catch (e, st) { + return Future.error( + OutputMessage( + message: 'failed to generate AWS headers', + additionalContext: e, + stackTrace: st, + level: Level.SEVERE, + ), + ); + } + } else { + headers = specConfig.headerDelegate.header(); + } final resp = await http.get(specConfig.url, headers: headers); if (resp.statusCode == 200) { diff --git a/openapi-generator/lib/src/models/command.dart b/openapi-generator/lib/src/models/command.dart index 2f847e0..6e3e112 100644 --- a/openapi-generator/lib/src/models/command.dart +++ b/openapi-generator/lib/src/models/command.dart @@ -1,7 +1,3 @@ -import 'dart:io'; - -import 'package:openapi_generator/src/determine_flutter_project_status.dart'; -import 'package:openapi_generator/src/gen_on_spec_changes.dart'; import 'package:openapi_generator_annotations/openapi_generator_annotations.dart'; /// Creates a representation of a cli request for Flutter or Dart. @@ -33,52 +29,3 @@ class Command { arguments, ); } - -/// CommandRunner provides an abstraction layer to external functions / processes. -class CommandRunner { - const CommandRunner(); - - Future runCommand({ - required Command command, - required String workingDirectory, - }) async => - Process.run( - command.executable, - command.arguments, - workingDirectory: workingDirectory, - runInShell: Platform.isWindows, - ); - - Future> loadAnnotatedFile({required String path}) async { - final f = File(path); - return f.readAsLines(); - } - - Future writeAnnotatedFile( - {required String path, required List content}) async { - final f = File(path); - return f.writeAsStringSync(content.join('\n'), flush: true); - } - - Future cacheSpecFile({ - required Map updatedSpec, - required String cachedPath, - }) async => - cacheSpec(outputLocation: cachedPath, spec: updatedSpec); - - Future> loadSpecFile( - {required InputSpec specConfig, bool isCached = false}) async => - loadSpec(specConfig: specConfig); - - Future isSpecFileDirty({ - required Map cachedSpec, - required Map loadedSpec, - }) async => - isSpecDirty(cachedSpec: cachedSpec, loadedSpec: loadedSpec); - - Future checkForFlutterEnvironemt( - {Wrapper? wrapper = Wrapper.none, - String? providedPubspecPath}) async => - checkPubspecAndWrapperForFlutterSupport( - wrapper: wrapper, providedPubspecPath: providedPubspecPath); -} diff --git a/openapi-generator/lib/src/models/generator_arguments.dart b/openapi-generator/lib/src/models/generator_arguments.dart index d82962c..c43c392 100644 --- a/openapi-generator/lib/src/models/generator_arguments.dart +++ b/openapi-generator/lib/src/models/generator_arguments.dart @@ -2,8 +2,10 @@ import 'dart:async'; import 'dart:io'; import 'package:logging/logging.dart'; +import 'package:openapi_generator/src/extensions/type_methods.dart'; import 'package:openapi_generator/src/models/output_message.dart'; import 'package:openapi_generator_annotations/openapi_generator_annotations.dart'; +import 'package:source_gen/source_gen.dart' as src_gen; import '../utils.dart'; @@ -72,7 +74,6 @@ class GeneratorArguments { /// Use the provided spec instead of one located in [Directory.current]. /// /// Default: openapi.(ya?ml) | openapi.json - @Deprecated('Use inputSpec instead.') String _inputFile; /// Provides an OAS spec file. @@ -113,61 +114,63 @@ class GeneratorArguments { final InlineSchemaOptions? inlineSchemaOptions; GeneratorArguments({ - required Openapi annotation, - bool? alwaysRun, - String? inputSpecFile, - InputSpec? inputSpec, - String? templateDirectory, - Generator? generator, - Map? typeMapping, - Map? importMapping, - Map? reservedWordsMapping, - Map? inlineSchemaNameMapping, + required src_gen.ConstantReader annotations, + bool alwaysRun = false, + String inputSpecFile = '', + InputSpec inputSpec = const InputSpec.empty(), + String templateDirectory = '', + Generator generator = Generator.dart, + Map typeMapping = const {}, + Map importMapping = const {}, + Map reservedWordsMapping = const {}, + Map inlineSchemaNameMapping = const {}, AdditionalProperties? additionalProperties, InlineSchemaOptions? inlineSchemaOptions, - bool? skipValidation, - bool? runSourceGen, + bool skipValidation = false, + bool runSourceGen = true, String? outputDirectory, - bool? fetchDependencies, - bool? useNextGen, + bool fetchDependencies = true, + bool useNextGen = false, String? cachePath, String? pubspecPath, - bool? isDebug, - }) : alwaysRun = alwaysRun ?? annotation.alwaysRun ?? true, - _inputFile = inputSpecFile ?? annotation.inputSpecFile, - templateDirectory = - templateDirectory ?? annotation.templateDirectory ?? '', - generator = generator ?? annotation.generatorName, + bool isDebug = false, + }) : alwaysRun = annotations.readPropertyOrDefault('alwaysRun', alwaysRun), + _inputFile = + annotations.readPropertyOrDefault('inputSpecFile', inputSpecFile), + templateDirectory = annotations.readPropertyOrDefault( + 'templateDirectory', templateDirectory), + generator = + annotations.readPropertyOrDefault('generatorName', generator), typeMappings = - typeMapping ?? annotation.typeMappings ?? {}, + annotations.readPropertyOrDefault('typeMappings', typeMapping), importMappings = - importMapping ?? annotation.importMappings ?? {}, - reservedWordsMappings = reservedWordsMapping ?? - annotation.reservedWordsMappings ?? - {}, - inlineSchemaNameMappings = inlineSchemaNameMapping ?? - annotation.inlineSchemaNameMappings ?? - {}, - additionalProperties = - additionalProperties ?? annotation.additionalProperties, - inlineSchemaOptions = inlineSchemaOptions, - // ?? annotations.readPropertyOrDefault( - // 'inlineSchemaOptions', inlineSchemaOptions), - skipValidation = - skipValidation ?? annotation.skipSpecValidation ?? false, - runSourceGen = runSourceGen ?? annotation.runSourceGenOnOutput ?? true, - shouldFetchDependencies = - fetchDependencies ?? annotation.fetchDependencies ?? true, - outputDirectory = annotation.outputDirectory ?? - outputDirectory ?? - Directory.current.path, - useNextGen = useNextGen ?? annotation.useNextGen, - cachePath = cachePath ?? annotation.cachePath ?? defaultCachedPath, - pubspecPath = annotation.projectPubspecPath ?? + annotations.readPropertyOrDefault('importMappings', importMapping), + reservedWordsMappings = annotations.readPropertyOrDefault( + 'reservedWordsMappings', reservedWordsMapping), + inlineSchemaNameMappings = annotations.readPropertyOrDefault( + 'inlineSchemaNameMappings', inlineSchemaNameMapping), + additionalProperties = annotations.readPropertyOrDefault( + 'additionalProperties', additionalProperties), + inlineSchemaOptions = annotations.readPropertyOrDefault( + 'inlineSchemaOptions', inlineSchemaOptions), + skipValidation = annotations.readPropertyOrDefault( + 'skipSpecValidation', skipValidation), + runSourceGen = annotations.readPropertyOrDefault( + 'runSourceGenOnOutput', runSourceGen), + shouldFetchDependencies = annotations.readPropertyOrDefault( + 'fetchDependencies', fetchDependencies), + outputDirectory = annotations.readPropertyOrDefault( + 'outputDirectory', outputDirectory ?? Directory.current.path), + useNextGen = + annotations.readPropertyOrDefault('useNextGen', useNextGen), + cachePath = annotations.readPropertyOrDefault( + 'cachePath', cachePath ?? defaultCachedPath), + pubspecPath = annotations.readPropertyOrDefault( + 'projectPubspecPath', pubspecPath ?? - '${Directory.current.path}${Platform.pathSeparator}pubspec.yaml', - isDebug = annotation.debugLogging, - inputSpec = inputSpec ?? annotation.inputSpec ?? InputSpec.empty(); + '${Directory.current.path}${Platform.pathSeparator}pubspec.yaml'), + isDebug = annotations.readPropertyOrDefault('debugLogging', isDebug), + inputSpec = annotations.readPropertyOrDefault('inputSpec', inputSpec); /// The stringified name of the [Generator]. String get generatorName => generator == Generator.dart diff --git a/openapi-generator/lib/src/openapi_generator_runner.dart b/openapi-generator/lib/src/openapi_generator_runner.dart index 4d06b22..7ddc15d 100755 --- a/openapi-generator/lib/src/openapi_generator_runner.dart +++ b/openapi-generator/lib/src/openapi_generator_runner.dart @@ -6,6 +6,8 @@ import 'dart:isolate'; import 'package:analyzer/dart/element/element.dart'; import 'package:build/build.dart'; import 'package:logging/logging.dart'; +import 'package:openapi_generator/src/determine_flutter_project_status.dart'; +import 'package:openapi_generator/src/gen_on_spec_changes.dart'; import 'package:openapi_generator/src/models/output_message.dart'; import 'package:openapi_generator/src/utils.dart'; import 'package:openapi_generator_annotations/openapi_generator_annotations.dart' @@ -18,20 +20,14 @@ import 'models/generator_arguments.dart'; class OpenapiGenerator extends GeneratorForAnnotation { @Deprecated('To be removed in next major version') final bool testMode; - final CommandRunner runner; - final Logger _log; - OpenapiGenerator({ - this.testMode = false, - this.runner = const CommandRunner(), - Logger? logger, - }) : _log = logger ?? Logger('OpenApiGenerator'); + OpenapiGenerator({this.testMode = false}); @override FutureOr generateForAnnotatedElement( Element element, ConstantReader annotations, BuildStep buildStep) async { logOutputMessage( - log: _log, + log: log, communication: OutputMessage( message: [ '\n', @@ -42,35 +38,51 @@ class OpenapiGenerator extends GeneratorForAnnotation { ), ); - if (element is! ClassElement) { - final friendlyName = element.displayName; + try { + if (element is! ClassElement) { + final friendlyName = element.displayName; - throw InvalidGenerationSourceError( - 'Generator cannot target `$friendlyName`.', - todo: 'Remove the [Openapi] annotation from `$friendlyName`.', - ); - } else { - final apiAnnotation = Reviver(annotations).toInstance() as annots.Openapi; + throw InvalidGenerationSourceError( + 'Generator cannot target `$friendlyName`.', + todo: 'Remove the [Openapi] annotation from `$friendlyName`.', + ); + } else { + if (!(annotations.read('useNextGen').literalValue as bool)) { + if (annotations.read('cachePath').literalValue != null) { + throw InvalidGenerationSourceError( + 'useNextGen must be set when using cachePath', + todo: + 'Either set useNextGen: true on the annotation or remove the custom cachePath', + ); + } + } - if (!apiAnnotation.useNextGen && apiAnnotation.cachePath != null) { - throw AssertionError('useNextGen must be set when using cachePath'); - } - try { // Transform the annotations. - final args = GeneratorArguments(annotation: apiAnnotation); + final args = GeneratorArguments(annotations: annotations); + // Determine if the project has a dependency on the flutter sdk or not. - final baseCommand = await runner.checkForFlutterEnvironemt( + final baseCommand = await checkPubspecAndWrapperForFlutterSupport( wrapper: args.wrapper, providedPubspecPath: args.pubspecPath) ? 'flutter' : 'dart'; - logOutputMessage( - log: _log, - communication: OutputMessage( - message: 'Using $baseCommand environemnt', - ), - ); - if (args.useNextGen) { + if (!args.useNextGen) { + final path = + '${args.outputDirectory}${Platform.pathSeparator}lib${Platform.pathSeparator}api.dart'; + if (await File(path).exists()) { + if (!args.alwaysRun) { + logOutputMessage( + log: log, + communication: OutputMessage( + message: + 'Generated client already exists at [$path] and configuration is annotated with alwaysRun: [${args.alwaysRun}]. Therefore, skipping this build. Note that the "alwaysRun" config will be removed in future versions.', + level: Level.INFO, + ), + ); + return ''; + } + } + } else { // If the flag to use the next generation of the generator is applied // use the new functionality. return generatorV2( @@ -79,49 +91,33 @@ class OpenapiGenerator extends GeneratorForAnnotation { annotatedPath: buildStep.inputId.path); } - final path = - '${args.outputDirectory}${Platform.pathSeparator}lib${Platform.pathSeparator}api.dart'; - if (await File(path).exists()) { - if (!args.alwaysRun) { - logOutputMessage( - log: _log, - communication: OutputMessage( - message: - 'Generated client already exists at [$path] and configuration is annotated with alwaysRun: [${args.alwaysRun}]. Therefore, skipping this build. Note that the "alwaysRun" config will be removed in future versions.', - level: Level.INFO, - ), - ); - return ''; - } - } - await runOpenApiJar(arguments: args); await fetchDependencies(baseCommand: baseCommand, args: args); await generateSources(baseCommand: baseCommand, args: args); - } catch (e, st) { - late OutputMessage communication; - if (e is! OutputMessage) { - communication = OutputMessage( - message: '- There was an error generating the spec.', - level: Level.SEVERE, - additionalContext: e, - stackTrace: st, - ); - } else { - communication = e; - } - - logOutputMessage(log: _log, communication: communication); } - return ''; + } catch (e, st) { + late OutputMessage communication; + if (e is! OutputMessage) { + communication = OutputMessage( + message: '- There was an error generating the spec.', + level: Level.SEVERE, + additionalContext: e, + stackTrace: st, + ); + } else { + communication = e; + } + + logOutputMessage(log: log, communication: communication); } + return ''; } /// Runs the OpenAPI compiler with the given [args]. Future runOpenApiJar({required GeneratorArguments arguments}) async { final args = await arguments.jarArgs; logOutputMessage( - log: _log, + log: log, communication: OutputMessage( message: 'Running following command to generate openapi client - [ ${args.join(' ')} ]', @@ -135,18 +131,23 @@ class OpenapiGenerator extends GeneratorForAnnotation { // Include java environment variables in openApiCliCommand var javaOpts = Platform.environment['JAVA_OPTS'] ?? ''; - final result = await runner.runCommand( - command: Command( - executable: 'java', - arguments: [ + ProcessResult result; + if (!testMode) { + result = await Process.run( + 'java', + [ if (javaOpts.isNotEmpty) javaOpts, '-jar', binPath, ...args, ], - ), - workingDirectory: Directory.current.path, - ); + workingDirectory: Directory.current.path, + runInShell: Platform.isWindows, + ); + } else { + result = ProcessResult(999999, 0, null, null); + } + if (result.exitCode != 0) { return Future.error( OutputMessage( @@ -158,7 +159,7 @@ class OpenapiGenerator extends GeneratorForAnnotation { ); } else { logOutputMessage( - log: _log, + log: log, communication: OutputMessage( message: [ if (arguments.isDebug) result.stdout, @@ -181,7 +182,7 @@ class OpenapiGenerator extends GeneratorForAnnotation { required String annotatedPath}) async { if (args.isRemote) { logOutputMessage( - log: _log, + log: log, communication: OutputMessage( message: 'Using a remote specification, a cache will still be create but may be outdated.', @@ -192,14 +193,14 @@ class OpenapiGenerator extends GeneratorForAnnotation { try { if (!await hasDiff(args: args)) { logOutputMessage( - log: _log, + log: log, communication: OutputMessage( message: 'No diff between versions, not running generator.', ), ); } else { logOutputMessage( - log: _log, + log: log, communication: OutputMessage( message: 'Dirty Spec found. Running generation.', ), @@ -209,26 +210,26 @@ class OpenapiGenerator extends GeneratorForAnnotation { await generateSources(baseCommand: baseCommand, args: args); if (!args.hasLocalCache) { logOutputMessage( - log: _log, + log: log, communication: OutputMessage( message: 'No local cache found. Creating one.', - level: Level.INFO, + level: Level.CONFIG, ), ); } else { logOutputMessage( - log: _log, + log: log, communication: OutputMessage( message: 'Local cache found. Overwriting existing one.', - level: Level.INFO, + level: Level.CONFIG, ), ); } - await runner.cacheSpecFile( - cachedPath: args.cachePath, - updatedSpec: await runner.loadSpecFile(specConfig: args.inputSpec)); + await cacheSpec( + outputLocation: args.cachePath, + spec: await loadSpec(specConfig: args.inputSpec)); logOutputMessage( - log: _log, + log: log, communication: OutputMessage( message: 'Successfully cached spec changes.', ), @@ -236,7 +237,7 @@ class OpenapiGenerator extends GeneratorForAnnotation { } } catch (e, st) { logOutputMessage( - log: _log, + log: log, communication: OutputMessage( message: 'Failed to generate content.', additionalContext: e, @@ -248,7 +249,7 @@ class OpenapiGenerator extends GeneratorForAnnotation { await formatCode(args: args).then( (_) {}, onError: (e, st) => logOutputMessage( - log: _log, + log: log, communication: OutputMessage( message: 'Failed to format generated code.', additionalContext: e, @@ -259,14 +260,14 @@ class OpenapiGenerator extends GeneratorForAnnotation { ); await updateAnnotatedFile(annotatedPath: annotatedPath).then( (_) => logOutputMessage( - log: _log, + log: log, communication: OutputMessage( message: 'Successfully updated annotated file.', - level: Level.INFO, + level: Level.CONFIG, ), ), onError: (e, st) => logOutputMessage( - log: _log, + log: log, communication: OutputMessage( message: 'Failed to update annotated class file.', level: Level.WARNING, @@ -282,12 +283,12 @@ class OpenapiGenerator extends GeneratorForAnnotation { /// Load both specs into memory and verify if there is a diff between them. FutureOr hasDiff({required GeneratorArguments args}) async { try { - final cachedSpec = await runner.loadSpecFile( + final cachedSpec = await loadSpec( specConfig: annots.InputSpec(path: args.cachePath), isCached: true); - final loadedSpec = await runner.loadSpecFile(specConfig: args.inputSpec); + final loadedSpec = await loadSpec(specConfig: args.inputSpec); logOutputMessage( - log: _log, + log: log, communication: OutputMessage( message: [ 'Loaded cached and current spec files.', @@ -299,8 +300,7 @@ class OpenapiGenerator extends GeneratorForAnnotation { ), ); - return await runner.isSpecFileDirty( - cachedSpec: cachedSpec, loadedSpec: loadedSpec); + return isSpecDirty(cachedSpec: cachedSpec, loadedSpec: loadedSpec); } catch (e, st) { return Future.error( OutputMessage( @@ -319,7 +319,7 @@ class OpenapiGenerator extends GeneratorForAnnotation { {required String baseCommand, required GeneratorArguments args}) async { if (!args.runSourceGen) { logOutputMessage( - log: _log, + log: log, communication: OutputMessage( message: 'Skipping source gen step due to flag being set.', level: Level.WARNING, @@ -327,7 +327,7 @@ class OpenapiGenerator extends GeneratorForAnnotation { ); } else if (!args.shouldGenerateSources) { logOutputMessage( - log: _log, + log: log, communication: OutputMessage( message: 'Skipping source gen because generator does not need it.', ), @@ -335,7 +335,7 @@ class OpenapiGenerator extends GeneratorForAnnotation { } else { return await runSourceGen(baseCommand: baseCommand, args: args).then( (_) => logOutputMessage( - log: _log, + log: log, communication: OutputMessage( message: 'Sources generated successfully.', ), @@ -356,7 +356,7 @@ class OpenapiGenerator extends GeneratorForAnnotation { Future runSourceGen( {required String baseCommand, required GeneratorArguments args}) async { logOutputMessage( - log: _log, + log: log, communication: OutputMessage( message: 'Running source code generation.', ), @@ -369,14 +369,23 @@ class OpenapiGenerator extends GeneratorForAnnotation { wrapper: args.wrapper); logOutputMessage( - log: _log, + log: log, communication: OutputMessage( message: '${command.executable} ${command.arguments.join(' ')}', ), ); - final results = await runner.runCommand( - command: command, workingDirectory: args.outputDirectory); + ProcessResult results; + if (!testMode) { + results = await Process.run( + command.executable, + command.arguments, + runInShell: Platform.isWindows, + workingDirectory: args.outputDirectory, + ); + } else { + results = ProcessResult(99999, 0, null, null); + } if (results.exitCode != 0) { return Future.error( @@ -389,7 +398,7 @@ class OpenapiGenerator extends GeneratorForAnnotation { ); } else { logOutputMessage( - log: _log, + log: log, communication: OutputMessage( message: 'Codegen completed successfully.', ), @@ -402,7 +411,7 @@ class OpenapiGenerator extends GeneratorForAnnotation { {required String baseCommand, required GeneratorArguments args}) async { if (!args.shouldFetchDependencies) { logOutputMessage( - log: _log, + log: log, communication: OutputMessage( message: 'Skipping install step because flag was set.', level: Level.WARNING, @@ -415,15 +424,25 @@ class OpenapiGenerator extends GeneratorForAnnotation { wrapper: args.wrapper); logOutputMessage( - log: _log, + log: log, communication: OutputMessage( message: 'Installing dependencies with generated source. ${command.executable} ${command.arguments.join(' ')}', ), ); - final results = await runner.runCommand( - command: command, workingDirectory: args.outputDirectory); + ProcessResult results; + if (!testMode) { + results = await Process.run( + command.executable, + command.arguments, + runInShell: Platform.isWindows, + workingDirectory: args.outputDirectory, + ); + } else { + results = ProcessResult(999999, 0, null, null); + } + if (results.exitCode != 0) { return Future.error( OutputMessage( @@ -435,7 +454,7 @@ class OpenapiGenerator extends GeneratorForAnnotation { ); } else { logOutputMessage( - log: _log, + log: log, communication: OutputMessage( message: [ if (args.isDebug) results.stdout, @@ -452,35 +471,35 @@ class OpenapiGenerator extends GeneratorForAnnotation { required Map updatedSpec, required String cachedPath, }) async => - runner.cacheSpecFile(updatedSpec: updatedSpec, cachedPath: cachedPath); + cacheSpec(spec: updatedSpec, outputLocation: cachedPath); Future updateAnnotatedFile({required annotatedPath}) async { + // The should exist since that is what triggered the build to begin with so + // there is no point in verifying it exists. It is also a relative file since + // it exists within the project. + final f = File(annotatedPath); + var content = f.readAsLinesSync(); + final now = DateTime.now().toIso8601String(); + final generated = '$lastRunPlaceHolder: $now'; + if (content.first.contains(lastRunPlaceHolder)) { + content = content.sublist(1); + logOutputMessage( + log: log, + communication: OutputMessage( + message: 'Found generated timestamp. Updating with $now', + ), + ); + } else { + logOutputMessage( + log: log, + communication: OutputMessage( + message: 'Creating generated timestamp with $now', + ), + ); + } try { - // The should exist since that is what triggered the build to begin with so - // there is no point in verifying it exists. It is also a relative file since - // it exists within the project. - var content = await runner.loadAnnotatedFile(path: annotatedPath); - final now = DateTime.now().toIso8601String(); - final generated = '$lastRunPlaceHolder: $now'; - if (content.first.contains(lastRunPlaceHolder)) { - content = content.sublist(1); - logOutputMessage( - log: _log, - communication: OutputMessage( - message: 'Found generated timestamp. Updating with $now', - ), - ); - } else { - logOutputMessage( - log: _log, - communication: OutputMessage( - message: 'Creating generated timestamp with $now', - ), - ); - } - - await runner.writeAnnotatedFile( - path: annotatedPath, content: content..insert(0, generated)); + content.insert(0, generated); + f.writeAsStringSync(content.join('\n'), flush: true); } catch (e, st) { return Future.error( OutputMessage( @@ -496,8 +515,17 @@ class OpenapiGenerator extends GeneratorForAnnotation { /// Format the generated code in the output directory. Future formatCode({required GeneratorArguments args}) async { final command = Command(executable: 'dart', arguments: ['format', './']); - final result = await runner.runCommand( - command: command, workingDirectory: args.outputDirectory); + ProcessResult result; + if (!testMode) { + result = await Process.run( + command.executable, + command.arguments, + workingDirectory: args.outputDirectory, + runInShell: Platform.isWindows, + ); + } else { + result = ProcessResult(99999, 0, null, null); + } if (result.exitCode != 0) { return Future.error( @@ -510,11 +538,9 @@ class OpenapiGenerator extends GeneratorForAnnotation { ); } else { logOutputMessage( - log: _log, - communication: OutputMessage( - message: 'Successfully formatted code.', - ), - ); + log: log, + communication: + OutputMessage(message: 'Successfully formatted code.')); } } } diff --git a/openapi-generator/lib/src/utils.dart b/openapi-generator/lib/src/utils.dart index 9620b36..40f344a 100644 --- a/openapi-generator/lib/src/utils.dart +++ b/openapi-generator/lib/src/utils.dart @@ -6,7 +6,7 @@ import 'models/output_message.dart'; /// A utility function that prints out a log meant for the end user. void logOutputMessage( {required Logger log, required OutputMessage communication}) => - log.log(communication.level, communication.message, + log.log(communication.level, communication.message + '\n', communication.additionalContext, communication.stackTrace); /// Transforms a [Map] into a string. diff --git a/openapi-generator/pubspec.yaml b/openapi-generator/pubspec.yaml index 83c1451..60ed7e7 100755 --- a/openapi-generator/pubspec.yaml +++ b/openapi-generator/pubspec.yaml @@ -24,13 +24,7 @@ dev_dependencies: source_gen_test: pedantic: coverage: ^1.6.3 - mockito: -dependency_overrides: - openapi_generator_annotations: - path: ../openapi-generator-annotations - source_gen: - git: - ref: reviver - url: https://github.com/Nexushunter/source_gen.git - path: source_gen \ No newline at end of file +#dependency_overrides: +# openapi_generator_annotations: +# path: ../openapi-generator-annotations diff --git a/openapi-generator/test/builder_test.dart b/openapi-generator/test/builder_test.dart new file mode 100644 index 0000000..c97b621 --- /dev/null +++ b/openapi-generator/test/builder_test.dart @@ -0,0 +1,588 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:build_test/build_test.dart'; +import 'package:openapi_generator/src/gen_on_spec_changes.dart'; +import 'package:openapi_generator/src/models/generator_arguments.dart'; +import 'package:openapi_generator/src/utils.dart'; +import 'package:openapi_generator_annotations/openapi_generator_annotations.dart'; +import 'package:source_gen/source_gen.dart'; +import 'package:test/expect.dart'; +import 'package:test/scaffolding.dart'; + +import 'utils.dart'; + +/// We test the build runner by mocking the specs and then checking the output +/// content for the expected generate command. +void main() { + group('generator dio', () { + test('to generate appropriate openapi cli command', () async { + expect( + await generate(''' + @Openapi( + additionalProperties: + DioProperties(pubName: 'petstore_api', pubAuthor: 'Johnny dep...'), + inputSpecFile: '../openapi-spec.yaml', + typeMappings: {'Pet': 'ExamplePet'}, + generatorName: Generator.dio, + runSourceGenOnOutput: true, + alwaysRun: true, + outputDirectory: 'api/petstore_api') + '''), + contains( + 'generate -o=api/petstore_api -i=../openapi-spec.yaml -g=dart-dio --type-mappings=Pet=ExamplePet --additional-properties=allowUnicodeIdentifiers=false,ensureUniqueParams=true,useEnumExtension=true,prependFormOrBodyParameters=false,pubAuthor=Johnny dep...,pubName=petstore_api,legacyDiscriminatorBehavior=true,sortModelPropertiesByRequiredFlag=true,sortParamsByRequiredFlag=true,wrapper=none')); + }); + + test('to generate command with import and type mappings', () async { + expect( + await generate(''' + @Openapi( + inputSpecFile: '../openapi-spec.yaml', + typeMappings: {'int-or-string':'IntOrString'}, + importMappings: {'IntOrString':'./int_or_string.dart'}, + generatorName: Generator.dio, + outputDirectory: '${testSpecPath}output', + ) + '''), + contains( + 'generate -o=${testSpecPath}output -i=../openapi-spec.yaml -g=dart-dio --import-mappings=IntOrString=./int_or_string.dart --type-mappings=int-or-string=IntOrString')); + }); + + test('to generate command with inline schema mappings', () async { + expect( + await generate(''' + @Openapi( + inputSpecFile: '../openapi-spec.yaml', + typeMappings: {'int-or-string':'IntOrString'}, + inlineSchemaNameMappings: {'inline_object_2':'SomethingMapped','inline_object_4':'nothing_new'}, + generatorName: Generator.dio, + outputDirectory: '${testSpecPath}output', + ) + '''), + contains(''' + generate -o=${testSpecPath}output -i=../openapi-spec.yaml -g=dart-dio --inline-schema-name-mappings=inline_object_2=SomethingMapped,inline_object_4=nothing_new --type-mappings=int-or-string=IntOrString + ''' + .trim())); + }); + + // test('to generate command with inline schema options', () async { + // expect(await generate(''' + // @Openapi( + // inputSpecFile: '../openapi-spec.yaml', + // inlineSchemaOptions: InlineSchemaOptions(skipSchemaReuse: true,refactorAllofInlineSchemas: true,resolveInlineEnums: true), + // generatorName: Generator.dio) + // '''), contains(''' + // generate -i ../openapi-spec.yaml -g dart-dio --type-mappings=int-or-string=IntOrString --inline-schema-name-mappings=inline_object_2=SomethingMapped,inline_object_4=nothing_new + // '''.trim())); + // }); + }); + + group('generator dioAlt', () { + test('to generate appropriate openapi cli command', () async { + expect( + await generate(''' + @Openapi( + additionalProperties: + DioProperties(pubName: 'petstore_api', pubAuthor: 'Johnny dep...'), + inputSpecFile: '../openapi-spec.yaml', + typeMappings: {'Pet': 'ExamplePet'}, + generatorName: Generator.dio, + runSourceGenOnOutput: true, + alwaysRun: true, + outputDirectory: 'api/petstore_api') + '''), + contains(''' + generate -o=api/petstore_api -i=../openapi-spec.yaml -g=dart-dio --type-mappings=Pet=ExamplePet --additional-properties=allowUnicodeIdentifiers=false,ensureUniqueParams=true,useEnumExtension=true,prependFormOrBodyParameters=false,pubAuthor=Johnny dep...,pubName=petstore_api,legacyDiscriminatorBehavior=true,sortModelPropertiesByRequiredFlag=true,sortParamsByRequiredFlag=true,wrapper=none + ''' + .trim())); + }); + + test('to generate command with import and type mappings for dioAlt', + () async { + expect( + await generate(''' + @Openapi( + inputSpecFile: '../openapi-spec.yaml', + typeMappings: {'int-or-string':'IntOrString'}, + importMappings: {'IntOrString':'./int_or_string.dart'}, + generatorName: Generator.dioAlt, + outputDirectory: '${testSpecPath}output', + ) + '''), + contains( + 'generate -o=${testSpecPath}output -i=../openapi-spec.yaml -g=dart2-api --import-mappings=IntOrString=./int_or_string.dart --type-mappings=int-or-string=IntOrString')); + }); + }); + + group('NextGen', () { + late String generatedOutput; + final specPath = + 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml'; + final basePath = '${testSpecPath}output-nextgen/'; + final f = File('${basePath}cache.json'); + tearDown(() { + final b = File(basePath); + if (b.existsSync()) b.deleteSync(recursive: true); + }); + + group('runs', () { + setUpAll(() { + if (!f.existsSync()) { + f.createSync(recursive: true); + } + f.writeAsStringSync('{}'); + }); + tearDown(() { + if (f.existsSync()) { + f.deleteSync(); + } + }); + test('fails with invalid configuration', () async { + generatedOutput = await generate(''' + @Openapi( + inputSpecFile: '$specPath', + typeMappings: {'int-or-string':'IntOrString'}, + importMappings: {'IntOrString':'./int_or_string.dart'}, + generatorName: Generator.dioAlt, + useNextGen: false, + cachePath: '${f.path}', + outputDirectory: '${f.parent.path}/invalid_config/' + ) + '''); + expect(generatedOutput, + contains('useNextGen must be set when using cachePath')); + }); + test('Logs warning when using remote spec', () async { + generatedOutput = await generate(''' + @Openapi( + inputSpecFile: '$specPath', + inputSpec: RemoteSpec(path: '$specPath'), + typeMappings: {'int-or-string':'IntOrString'}, + importMappings: {'IntOrString':'./int_or_string.dart'}, + generatorName: Generator.dioAlt, + useNextGen: true, + outputDirectory: '${f.parent.path}/logs-when-remote' + ) + '''); + expect( + generatedOutput, + contains( + 'Using a remote specification, a cache will still be create but may be outdated.')); + }); + test('when the spec is dirty', () async { + final src = ''' + @Openapi( + inputSpecFile: '$specPath', + inputSpec: RemoteSpec(path: '$specPath'), + useNextGen: true, + cachePath: '${f.path}', + outputDirectory: '${f.parent.path}/when-spec-is-dirty' + ) + '''; + generatedOutput = await generate(src); + expect( + generatedOutput, contains('Dirty Spec found. Running generation.')); + }); + test('and terminates early when there is no diff', () async { + f.writeAsStringSync( + jsonEncode(await loadSpec(specConfig: RemoteSpec(path: specPath)))); + final src = ''' + @Openapi( + inputSpecFile: '$specPath', + inputSpec: RemoteSpec(path: '$specPath'), + useNextGen: true, + cachePath: '${f.path}', + outputDirectory: '${f.parent.path}/early-term' + ) + '''; + generatedOutput = await generate(src); + expect(generatedOutput, + contains('No diff between versions, not running generator.')); + }); + test('openApiJar with expected args', () async { + f.writeAsStringSync(jsonEncode({'someKey': 'someValue'})); + final annotations = (await resolveSource( + File('$testSpecPath/next_gen_builder_test_config.dart') + .readAsStringSync(), + (resolver) async => + (await resolver.findLibraryByName('test_lib'))!)) + .getClass('TestClassConfig')! + .metadata + .map((e) => ConstantReader(e.computeConstantValue()!)) + .first; + final args = GeneratorArguments(annotations: annotations); + generatedOutput = await generate(''' + @Openapi( + inputSpecFile: + 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml', + inputSpec: RemoteSpec(path: '$specPath'), + generatorName: Generator.dio, + useNextGen: true, + cachePath: '${f.path}', + outputDirectory: './test/specs/output-nextgen/expected-args' +) + '''); + expect( + generatedOutput, contains('[ ${(await args.jarArgs).join(' ')} ]')); + }); + test('adds generated comment', () async { + f.writeAsStringSync(jsonEncode({'someKey': 'someValue'})); + final contents = File('$testSpecPath/next_gen_builder_test_config.dart') + .readAsStringSync(); + final copy = + File('./test/specs/next_gen_builder_test_config_copy.dart'); + copy.writeAsStringSync(contents, flush: true); + generatedOutput = await generate(''' + @Openapi( + inputSpecFile: + 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml', + inputSpec: RemoteSpec(path: '$specPath'), + generatorName: Generator.dio, + useNextGen: true, + cachePath: '${f.path}', + outputDirectory: './test/specs/output-nextgen/add-generated-comment' +) + ''', path: copy.path); + + var hasOutput = copy.readAsStringSync().contains(lastRunPlaceHolder); + expect(generatedOutput, contains('Creating generated timestamp with ')); + + generatedOutput = await generate(''' + @Openapi( + inputSpecFile: + 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml', + inputSpec: RemoteSpec(path: '$specPath'), + generatorName: Generator.dio, + useNextGen: true, + cachePath: '${f.path}', + outputDirectory: './test/specs/output-nextgen/add-generated-comment' +) + ''', path: copy.path); + + hasOutput = copy.readAsStringSync().contains(lastRunPlaceHolder); + expect(generatedOutput, + contains('Found generated timestamp. Updating with')); + + copy.deleteSync(); + expect(hasOutput, isTrue); + }); + group('source gen', () { + group('uses Flutter', () { + group('with wrapper', () { + test('fvm', () async { + generatedOutput = await generate(''' +@Openapi( + inputSpecFile: + 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml', + inputSpec: RemoteSpec(path: '$specPath'), + generatorName: Generator.dio, + useNextGen: true, + cachePath: '${f.path}', + outputDirectory: '${f.parent.path}/fvm', + additionalProperties: AdditionalProperties( + wrapper: Wrapper.fvm, + ), +) + '''); + expect( + generatedOutput, contains('Running source code generation.')); + expect( + generatedOutput, + contains( + 'fvm pub run build_runner build --delete-conflicting-outputs')); + }); + test('flutterw', () async { + generatedOutput = await generate(''' +@Openapi( + inputSpecFile: + 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml', + inputSpec: RemoteSpec(path: '$specPath'), + generatorName: Generator.dio, + useNextGen: true, + cachePath: '${f.path}', + outputDirectory: '${f.parent.path}/flutterw', + additionalProperties: AdditionalProperties( + wrapper: Wrapper.flutterw, + ), +) + '''); + expect( + generatedOutput, contains('Running source code generation.')); + expect( + generatedOutput, + contains( + './flutterw pub run build_runner build --delete-conflicting-outputs')); + }); + }); + test('without wrapper', () async { + final annotations = (await resolveSource( + File('$testSpecPath/next_gen_builder_flutter_test_config.dart') + .readAsStringSync(), + (resolver) async => + (await resolver.findLibraryByName('test_lib'))!)) + .getClass('TestClassConfig')! + .metadata + .map((e) => ConstantReader(e.computeConstantValue()!)) + .first; + final args = GeneratorArguments(annotations: annotations); + generatedOutput = await generate(''' +@Openapi( + inputSpecFile: + 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml', + inputSpec: RemoteSpec(path: '$specPath'), + generatorName: Generator.dio, + useNextGen: true, + cachePath: '${f.path}', + outputDirectory: '${f.parent.path}/flutter', + projectPubspecPath: './test/specs/flutter_pubspec.test.yaml', +) + '''); + + expect(args.wrapper, Wrapper.none); + expect( + generatedOutput, contains('Running source code generation.')); + expect( + generatedOutput, + contains( + 'flutter pub run build_runner build --delete-conflicting-outputs')); + }); + }); + test('uses dart', () async { + final annotations = (await resolveSource( + File('$testSpecPath/next_gen_builder_test_config.dart') + .readAsStringSync(), + (resolver) async => + (await resolver.findLibraryByName('test_lib'))!)) + .getClass('TestClassConfig')! + .metadata + .map((e) => ConstantReader(e.computeConstantValue()!)) + .first; + final args = GeneratorArguments(annotations: annotations); + generatedOutput = await generate(''' +@Openapi( + inputSpecFile: + 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml', + inputSpec: RemoteSpec(path: '$specPath'), + generatorName: Generator.dio, + useNextGen: true, + cachePath: '${f.path}', + outputDirectory: '${f.parent.path}/dart', + projectPubspecPath: './test/specs/dart_pubspec.test.yaml', +) + '''); + + expect(args.wrapper, Wrapper.none); + expect(generatedOutput, contains('Running source code generation.')); + expect( + generatedOutput, + contains( + 'dart pub run build_runner build --delete-conflicting-outputs')); + }); + group('except when', () { + test('flag is set', () async { + final annotations = (await resolveSource( + ''' +library test_lib; + +import 'package:openapi_generator_annotations/openapi_generator_annotations.dart'; + +@Openapi( + inputSpecFile: + 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml', + inputSpec: RemoteSpec(path: '$specPath'), + generatorName: Generator.dio, + useNextGen: true, + cachePath: '${f.path}', + outputDirectory: '${f.parent.path}/no-src', + runSourceGenOnOutput: false, +) +class TestClassConfig extends OpenapiGeneratorConfig {} + ''', + (resolver) async => + (await resolver.findLibraryByName('test_lib'))!)) + .getClass('TestClassConfig')! + .metadata + .map((e) => ConstantReader(e.computeConstantValue()!)) + .first; + final args = GeneratorArguments(annotations: annotations); + + expect(args.runSourceGen, isFalse); + generatedOutput = await generate(''' +@Openapi( + inputSpecFile: + 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml', + inputSpec: RemoteSpec(path: '$specPath'), + generatorName: Generator.dio, + useNextGen: true, + cachePath: '${f.path}', + outputDirectory: '${f.parent.path}/no-src', + runSourceGenOnOutput: false, +) + '''); + expect(generatedOutput, + contains('Skipping source gen step due to flag being set.')); + }); + test('generator is dart', () async { + final annotations = (await resolveSource( + ''' +library test_lib; + +import 'package:openapi_generator_annotations/openapi_generator_annotations.dart'; + +@Openapi( + inputSpecFile: + 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml', + inputSpec: RemoteSpec(path: '$specPath'), + generatorName: Generator.dart, + useNextGen: true, + cachePath: '${f.path}', + outputDirectory: '${f.parent.path}/dart-gen' +) +class TestClassConfig extends OpenapiGeneratorConfig {} + ''', + (resolver) async => + (await resolver.findLibraryByName('test_lib'))!)) + .getClass('TestClassConfig')! + .metadata + .map((e) => ConstantReader(e.computeConstantValue()!)) + .first; + final args = GeneratorArguments(annotations: annotations); + expect(args.runSourceGen, isTrue); + generatedOutput = await generate(''' +@Openapi( + inputSpecFile: + 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml', + inputSpec: RemoteSpec(path: '$specPath'), + generatorName: Generator.dart, + useNextGen: true, + cachePath: '${f.path}', + outputDirectory: '${f.parent.path}/dart-gen' +) + '''); + expect( + generatedOutput, + contains( + 'Skipping source gen because generator does not need it.')); + }); + }); + test('logs when successful', () async { + generatedOutput = await generate(''' +@Openapi( + inputSpecFile: + 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml', + inputSpec: RemoteSpec(path: '$specPath'), + generatorName: Generator.dio, + useNextGen: true, + cachePath: '${f.path}', + outputDirectory: '${f.parent.path}/success', + projectPubspecPath: './test/specs/dart_pubspec.test.yaml', +) + '''); + expect(generatedOutput, contains('Codegen completed successfully.')); + expect(generatedOutput, contains('Sources generated successfully.')); + }); + }); + group('fetch dependencies', () { + test('except when flag is present', () async { + generatedOutput = await generate(''' +@Openapi( + inputSpecFile: + 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml', + inputSpec: RemoteSpec(path: '$specPath'), + generatorName: Generator.dio, + useNextGen: true, + cachePath: '${f.path}', + outputDirectory: '${f.parent.path}/no-fetch', + projectPubspecPath: './test/specs/dart_pubspec.test.yaml', + fetchDependencies: false, +) + '''); + expect(generatedOutput, + contains('Skipping install step because flag was set.')); + }); + test('succeeds', () async { + generatedOutput = await generate(''' +@Openapi( + inputSpecFile: + 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml', + inputSpec: RemoteSpec(path: '$specPath'), + generatorName: Generator.dio, + useNextGen: true, + cachePath: '${f.path}', + outputDirectory: '${f.parent.path}/no-fetch', + projectPubspecPath: './test/specs/dart_pubspec.test.yaml', +) + '''); + expect(generatedOutput, + contains('Installing dependencies with generated source.')); + expect(generatedOutput, contains('Install completed successfully.')); + }); + }); + group('update cache', () { + final src = ''' + @Openapi( + inputSpecFile: '$specPath', + inputSpec: RemoteSpec(path: '$specPath'), + useNextGen: true, + cachePath: '${f.path}', + outputDirectory: '${f.parent.path}/update-cache', + ) + '''; + + test('creating a cache file when not found', () async { + // Ensure that other tests don't make this available; + if (f.existsSync()) { + f.deleteSync(); + } + expect(f.existsSync(), isFalse); + generatedOutput = await generate(src); + expect(f.existsSync(), isTrue); + expect(jsonDecode(f.readAsStringSync()), + await loadSpec(specConfig: RemoteSpec(path: specPath))); + }); + test('updates the cache file when found', () async { + f.writeAsStringSync(jsonEncode({'someKey': 'someValue'})); + expect(f.existsSync(), isTrue); + generatedOutput = await generate(src); + final expectedSpec = + await loadSpec(specConfig: RemoteSpec(path: specPath)); + final actualSpec = jsonDecode(f.readAsStringSync()); + expect(actualSpec, expectedSpec); + }); + test('logs when successful', () async { + f.writeAsStringSync(jsonEncode({'someKey': 'someValue'})); + generatedOutput = await generate(src); + expect( + generatedOutput, contains('Successfully cached spec changes.')); + }); + }); + test('uses AWS', () async { + generatedOutput = await generate(''' +@Openapi( + inputSpecFile: '', + inputSpec: RemoteSpec( + path: + 'http://bucket.s3.us-east-1.localhost.localstack.cloud:4566/openapi.yaml', + headerDelegate: AWSRemoteSpecHeaderDelegate( + bucket: 'bucket', + accessKeyId: 'test', + secretAccessKey: 'test', + ), + ), + generatorName: Generator.dio, + useNextGen: true, + cachePath: '${f.path}', + outputDirectory: '${f.parent.path}/uses-aws-spec', + projectPubspecPath: './test/specs/dart_pubspec.test.yaml', +) + '''); + + expect(generatedOutput, contains('Running source code generation.')); + expect( + generatedOutput, + contains( + 'dart pub run build_runner build --delete-conflicting-outputs')); + }); + }); + }); +} diff --git a/openapi-generator/test/command_test.dart b/openapi-generator/test/command_test.dart index 23967fa..be502a7 100644 --- a/openapi-generator/test/command_test.dart +++ b/openapi-generator/test/command_test.dart @@ -1,11 +1,7 @@ -import 'dart:io'; - import 'package:openapi_generator/src/models/command.dart'; import 'package:openapi_generator_annotations/openapi_generator_annotations.dart'; import 'package:test/test.dart'; -import 'utils.dart'; - void main() { group('Command', () { final testArgs = ['pub', 'get']; @@ -36,80 +32,4 @@ void main() { expect(command.executable, 'dart'); }); }); - - group('CommandRunner', () { - final runner = CommandRunner(); - test( - 'runCommand returns a process', - () async => expect( - await runner.runCommand( - command: Command(executable: 'dart', arguments: ['--version']), - workingDirectory: './'), - isA(), - ), - ); - test('loads an annotated file', () async { - expect( - await runner.loadAnnotatedFile( - path: '$testSpecPath/next_gen_builder_test_config.dart'), - '''library test_lib; - -import 'package:openapi_generator_annotations/openapi_generator_annotations.dart'; - -@Openapi( - inputSpecFile: - 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml', - inputSpec: RemoteSpec( - path: - 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml', - ), - generatorName: Generator.dio, - useNextGen: true, - cachePath: './test/specs/output-nextgen/expected-args/cache.json', - outputDirectory: './test/specs/output-nextgen/expected-args') -class TestClassConfig {}''' - .split('\n'), - ); - }); - test( - 'writes annotation file', - () async => expect( - runner.writeAnnotatedFile( - path: '$testSpecPath/next_gen_builder_test_config.dart', - content: '''library test_lib; - -import 'package:openapi_generator_annotations/openapi_generator_annotations.dart'; - -@Openapi( - inputSpecFile: - 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml', - inputSpec: RemoteSpec( - path: - 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml', - ), - generatorName: Generator.dio, - useNextGen: true, - cachePath: './test/specs/output-nextgen/expected-args/cache.json', - outputDirectory: './test/specs/output-nextgen/expected-args') -class TestClassConfig {}''' - .split('\n')), - completes)); - test( - 'writes cache spec file', - () async => expect( - runner.cacheSpecFile( - updatedSpec: {}, - cachedPath: '$testSpecPath/commands-cache.json'), - completes)); - test( - 'checks for flutter environment', - () async => expect( - await runner.checkForFlutterEnvironemt(wrapper: Wrapper.fvm), - isTrue)); - test( - 'checks spec dirty status', - () async => expect( - await runner.isSpecFileDirty(cachedSpec: {}, loadedSpec: {}), - isFalse)); - }); } diff --git a/openapi-generator/test/generator_arguments_test.dart b/openapi-generator/test/generator_arguments_test.dart index 622b618..53712e9 100644 --- a/openapi-generator/test/generator_arguments_test.dart +++ b/openapi-generator/test/generator_arguments_test.dart @@ -1,18 +1,19 @@ import 'dart:io'; +import 'package:build_test/build_test.dart'; import 'package:openapi_generator/src/models/generator_arguments.dart'; import 'package:openapi_generator/src/models/output_message.dart'; import 'package:openapi_generator/src/utils.dart'; import 'package:openapi_generator_annotations/openapi_generator_annotations.dart'; +import 'package:source_gen/source_gen.dart' as src_gen; import 'package:test/test.dart'; void main() { group('GeneratorArguments', () { group('defaults', () { late GeneratorArguments args; - setUpAll(() => args = GeneratorArguments( - annotation: - Openapi(inputSpecFile: '', generatorName: Generator.dart))); + setUpAll(() => + args = GeneratorArguments(annotations: src_gen.ConstantReader(null))); test('alwaysRun', () => expect(args.alwaysRun, isFalse)); test('useNextGen', () => expect(args.useNextGen, isFalse)); test('cachePath', () => expect(args.cachePath, defaultCachedPath)); @@ -81,11 +82,9 @@ void main() { }); }); group('accepts overrides', () { + final annos = src_gen.ConstantReader(null); final args = GeneratorArguments( - annotation: Openapi( - inputSpecFile: '', - generatorName: Generator.dart, - ), + annotations: annos, alwaysRun: true, useNextGen: true, cachePath: 'test', @@ -101,9 +100,7 @@ void main() { typeMapping: {'package': 'type'}, reservedWordsMapping: {'const': 'final'}, inlineSchemaNameMapping: {'L': 'R'}, - additionalProperties: AdditionalProperties( - wrapper: Wrapper.fvm, - ), + additionalProperties: AdditionalProperties(wrapper: Wrapper.fvm), pubspecPath: 'testing/pubspec.yaml', ); test('alwaysRun', () => expect(args.alwaysRun, isTrue)); @@ -156,59 +153,155 @@ void main() { ), ); }); - test('uses config', () async { - final annotation = Openapi( - inputSpecFile: './openapi.test.yaml', - inputSpec: InputSpec(path: './test/specs/openapi.test.yaml'), - generatorName: Generator.dio, - useNextGen: true, - cachePath: './test/specs/output/cache.json', - typeMappings: {'key': 'value'}, - templateDirectory: 'template', - alwaysRun: true, - outputDirectory: './test/specs/output', - runSourceGenOnOutput: true, - apiPackage: 'test', - skipSpecValidation: false, - importMappings: {'package': 'test'}, - reservedWordsMappings: {'const': 'final'}, - additionalProperties: AdditionalProperties(wrapper: Wrapper.fvm), - inlineSchemaNameMappings: {'200resp': 'OkResp'}, - overwriteExistingFiles: true, - projectPubspecPath: './test/specs/dart_pubspec.test.yaml', - ); - final args = GeneratorArguments(annotation: annotation); - expect(args.alwaysRun, isTrue); - expect(args.useNextGen, isTrue); - expect(args.cachePath, './test/specs/output/cache.json'); - expect(args.outputDirectory, './test/specs/output'); - expect(args.runSourceGen, isTrue); - expect(args.shouldFetchDependencies, isTrue); - expect(args.skipValidation, isFalse); - expect(await args.inputFileOrFetch, './test/specs/openapi.test.yaml'); - expect(args.templateDirectory, 'template'); - expect(args.generator, Generator.dio); - expect(args.wrapper, Wrapper.fvm); - expect(args.importMappings, {'package': 'test'}); - expect(args.typeMappings, {'key': 'value'}); - expect(args.reservedWordsMappings, {'const': 'final'}); - expect(args.inlineSchemaNameMappings, {'200resp': 'OkResp'}); - expect(args.pubspecPath, './test/specs/dart_pubspec.test.yaml'); - expect(args.isRemote, isFalse); - expect(args.generatorName, 'dart-dio'); - expect(args.shouldGenerateSources, isTrue); - expect(await args.jarArgs, [ - 'generate', - '-o=${args.outputDirectory}', - '-i=${await args.inputFileOrFetch}', - '-t=${args.templateDirectory}', - '-g=${args.generatorName}', - '--reserved-words-mappings=${args.reservedWordsMappings.entries.fold('', foldStringMap())}', - '--inline-schema-name-mappings=${args.inlineSchemaNameMappings.entries.fold('', foldStringMap())}', - '--import-mappings=${args.importMappings.entries.fold('', foldStringMap())}', - '--type-mappings=${args.typeMappings.entries.fold('', foldStringMap())}', - '--additional-properties=${args.additionalProperties!.toMap().entries.fold('', foldStringMap(keyModifier: convertToPropertyKey))}' - ]); + group('annotation specification', () { + // https://github.com/gibahjoe/openapi-generator-dart/issues/110 + test('Processes annotations correctly', () async { + final config = File( + '${Directory.current.path}${Platform.pathSeparator}test${Platform.pathSeparator}specs${Platform.pathSeparator}test_config.dart') + .readAsStringSync(); + final annotations = (await resolveSource( + config, + (resolver) async => + (await resolver.findLibraryByName('test_lib'))!)) + .getClass('TestClassConfig')! + .metadata + .map((e) => src_gen.ConstantReader(e.computeConstantValue()!)) + .first; + final args = GeneratorArguments(annotations: annotations); + expect(args.alwaysRun, isTrue); + expect(args.useNextGen, isTrue); + expect(args.cachePath, './test/specs/output/cache.json'); + expect(args.outputDirectory, './test/specs/output'); + expect(args.runSourceGen, isTrue); + expect(args.shouldFetchDependencies, isTrue); + expect(args.skipValidation, isFalse); + expect(await args.inputFileOrFetch, './test/specs/openapi.test.yaml'); + expect(args.templateDirectory, 'template'); + expect(args.generator, Generator.dio); + expect(args.wrapper, Wrapper.fvm); + expect(args.importMappings, {'package': 'test'}); + expect(args.typeMappings, {'key': 'value'}); + expect(args.reservedWordsMappings, {'const': 'final'}); + expect(args.inlineSchemaNameMappings, {'200resp': 'OkResp'}); + expect(args.pubspecPath, './test/specs/dart_pubspec.test.yaml'); + expect(args.isRemote, isFalse); + expect(args.generatorName, 'dart-dio'); + expect(args.shouldGenerateSources, isTrue); + expect(args.additionalProperties?.useEnumExtension, isTrue); + expect(args.additionalProperties?.pubAuthor, 'test author'); + expect(await args.jarArgs, [ + 'generate', + '-o=${args.outputDirectory}', + '-i=${await args.inputFileOrFetch}', + '-t=${args.templateDirectory}', + '-g=${args.generatorName}', + '--reserved-words-mappings=${args.reservedWordsMappings.entries.fold('', foldStringMap())}', + '--inline-schema-name-mappings=${args.inlineSchemaNameMappings.entries.fold('', foldStringMap())}', + '--import-mappings=${args.importMappings.entries.fold('', foldStringMap())}', + '--type-mappings=${args.typeMappings.entries.fold('', foldStringMap())}', + '--additional-properties=${args.additionalProperties!.toMap().entries.fold('', foldStringMap(keyModifier: convertToPropertyKey))}' + ]); + }); + test('Processes annotation with DioProperties correctly', () async { + final config = File( + '${Directory.current.path}${Platform.pathSeparator}test${Platform.pathSeparator}specs${Platform.pathSeparator}dio_properties_test_config.dart') + .readAsStringSync(); + final annotations = (await resolveSource( + config, + (resolver) async => + (await resolver.findLibraryByName('test_lib'))!)) + .getClass('DioPropertiesTestConfig')! + .metadata + .map((e) => src_gen.ConstantReader(e.computeConstantValue()!)) + .first; + final args = GeneratorArguments(annotations: annotations); + expect(args.alwaysRun, isTrue); + expect(args.useNextGen, isTrue); + expect(args.cachePath, './test/specs/output/cache.json'); + expect(args.outputDirectory, './test/specs/output'); + expect(args.runSourceGen, isTrue); + expect(args.shouldFetchDependencies, isTrue); + expect(args.skipValidation, isFalse); + expect(await args.inputFileOrFetch, './test/specs/openapi.test.yaml'); + expect(args.templateDirectory, 'template'); + expect(args.generator, Generator.dio); + expect(args.wrapper, Wrapper.fvm); + expect(args.importMappings, {'package': 'test'}); + expect(args.typeMappings, {'key': 'value'}); + expect(args.reservedWordsMappings, {'const': 'final'}); + expect(args.inlineSchemaNameMappings, {'200resp': 'OkResp'}); + expect(args.pubspecPath, './test/specs/dart_pubspec.test.yaml'); + expect(args.isRemote, isFalse); + expect(args.generatorName, 'dart-dio'); + expect(args.shouldGenerateSources, isTrue); + expect(args.additionalProperties?.useEnumExtension, isTrue); + expect((args.additionalProperties as DioProperties?)?.nullableFields, + isTrue); + expect(await args.jarArgs, [ + 'generate', + '-o=${args.outputDirectory}', + '-i=${await args.inputFileOrFetch}', + '-t=${args.templateDirectory}', + '-g=${args.generatorName}', + '--reserved-words-mappings=${args.reservedWordsMappings.entries.fold('', foldStringMap())}', + '--inline-schema-name-mappings=${args.inlineSchemaNameMappings.entries.fold('', foldStringMap())}', + '--import-mappings=${args.importMappings.entries.fold('', foldStringMap())}', + '--type-mappings=${args.typeMappings.entries.fold('', foldStringMap())}', + '--additional-properties=${args.additionalProperties!.toMap().entries.fold('', foldStringMap(keyModifier: convertToPropertyKey))}' + ]); + }); + test('Processes annotation with DioAltProperties correctly', () async { + final config = File( + '${Directory.current.path}${Platform.pathSeparator}test${Platform.pathSeparator}specs${Platform.pathSeparator}dio_alt_properties_test_config.dart') + .readAsStringSync(); + final annotations = (await resolveSource( + config, + (resolver) async => + (await resolver.findLibraryByName('test_lib'))!)) + .getClass('DioAltPropertiesTestConfig')! + .metadata + .map((e) => src_gen.ConstantReader(e.computeConstantValue()!)) + .first; + final args = GeneratorArguments(annotations: annotations); + expect(args.alwaysRun, isTrue); + expect(args.useNextGen, isTrue); + expect(args.cachePath, './test/specs/output/cache.json'); + expect(args.outputDirectory, './test/specs/output'); + expect(args.runSourceGen, isTrue); + expect(args.shouldFetchDependencies, isTrue); + expect(args.skipValidation, isFalse); + expect(await args.inputFileOrFetch, './test/specs/openapi.test.yaml'); + expect(args.templateDirectory, 'template'); + expect(args.generator, Generator.dio); + expect(args.wrapper, Wrapper.fvm); + expect(args.importMappings, {'package': 'test'}); + expect(args.typeMappings, {'key': 'value'}); + expect(args.reservedWordsMappings, {'const': 'final'}); + expect(args.inlineSchemaNameMappings, {'200resp': 'OkResp'}); + expect(args.pubspecPath, './test/specs/dart_pubspec.test.yaml'); + expect(args.isRemote, isFalse); + expect(args.generatorName, 'dart-dio'); + expect(args.shouldGenerateSources, isTrue); + expect(args.additionalProperties?.useEnumExtension, isTrue); + expect( + (args.additionalProperties as DioAltProperties?)?.nullSafe, isTrue); + expect( + (args.additionalProperties as DioAltProperties?) + ?.nullSafeArrayDefault, + isTrue); + expect(await args.jarArgs, [ + 'generate', + '-o=${args.outputDirectory}', + '-i=${await args.inputFileOrFetch}', + '-t=${args.templateDirectory}', + '-g=${args.generatorName}', + '--reserved-words-mappings=${args.reservedWordsMappings.entries.fold('', foldStringMap())}', + '--inline-schema-name-mappings=${args.inlineSchemaNameMappings.entries.fold('', foldStringMap())}', + '--import-mappings=${args.importMappings.entries.fold('', foldStringMap())}', + '--type-mappings=${args.typeMappings.entries.fold('', foldStringMap())}', + '--additional-properties=${args.additionalProperties!.toMap().entries.fold('', foldStringMap(keyModifier: convertToPropertyKey))}' + ]); + }); }); }); } diff --git a/openapi-generator/test/generator_test.dart b/openapi-generator/test/generator_test.dart deleted file mode 100644 index efa9937..0000000 --- a/openapi-generator/test/generator_test.dart +++ /dev/null @@ -1,1100 +0,0 @@ -import 'dart:io'; - -import 'package:analyzer/dart/element/type.dart'; -import 'package:logging/logging.dart'; -import 'package:mockito/mockito.dart'; -import 'package:openapi_generator/src/models/command.dart'; -import 'package:openapi_generator/src/models/generator_arguments.dart'; -import 'package:openapi_generator/src/models/output_message.dart'; -import 'package:openapi_generator/src/openapi_generator_runner.dart'; -import 'package:openapi_generator/src/utils.dart'; -import 'package:openapi_generator_annotations/openapi_generator_annotations.dart'; -import 'package:source_gen/source_gen.dart' as src_gen; -import 'package:test/test.dart'; - -import 'mocks.mocks.dart'; -import 'utils.dart'; - -void main() { - group('OpenApiGenerator', () { - group('NextGen', () { - late src_gen.ConstantReader defaultAnnotations; - late Openapi annotation; - late GeneratorArguments realArguments; - late MockGeneratorArguments mockedArgs; - late MockCommandRunner mockRunner; - final logger = Logger('TestOpenApiGenerator'); - setUpAll(() async { - resetMockitoState(); - mockedArgs = MockGeneratorArguments(); - mockRunner = MockCommandRunner(); - defaultAnnotations = - await loadAnnoation('next_gen_builder_test_config.dart'); - annotation = src_gen.Reviver(defaultAnnotations).toInstance(); - realArguments = GeneratorArguments(annotation: annotation); - }); - - test('should have banner logger', () async { - final logs = []; - logger.onRecord.listen(logs.add); - try { - await OpenapiGenerator(logger: logger).generateForAnnotatedElement( - MockMethodElement(), defaultAnnotations, MockBuildStep()); - fail('Should throw when not ClassElement'); - } catch (_, __) { - expect(logs.length, 1); - expect( - logs[0].message, - contains([ - ':::::::::::::::::::::::::::::::::::::::::::', - ':: Openapi generator for dart ::', - ':::::::::::::::::::::::::::::::::::::::::::', - ].join('\n'))); - expect(logs[0].level, Level.INFO); - } - }); - - test('throws InvalidGenerationSourceError when not a class', () async { - try { - await OpenapiGenerator().generateForAnnotatedElement( - MockMethodElement(), defaultAnnotations, MockBuildStep()); - fail('Should throw when not ClassElement'); - } catch (e, _) { - expect(e, isA()); - e as src_gen.InvalidGenerationSourceError; - expect(e.message, 'Generator cannot target ``.'); - expect(e.todo, 'Remove the [Openapi] annotation from ``.'); - } - }); - - test('throws AssertionError when useCache is set but useNextGen is not', - () async { - try { - await OpenapiGenerator().generateForAnnotatedElement( - MockClassElement(), - await loadAnnoation('next_gen_builder_test_invalid_config.dart'), - MockBuildStep()); - fail('Should throw when useNextGen is false and cache path is set.'); - } catch (e, _) { - expect(e, isA()); - e as AssertionError; - expect(e.message, 'useNextGen must be set when using cachePath'); - } - }); - - group('logs which enviroment being used', () { - setUpAll(() => resetMockitoState()); - test('dart when wrapper is none', () async { - final logs = []; - logger.onRecord.listen(logs.add); - when(mockRunner.checkForFlutterEnvironemt( - wrapper: anyNamed('wrapper'), - providedPubspecPath: anyNamed('providedPubspecPath'))) - .thenAnswer((_) async => false); - - await OpenapiGenerator(logger: logger, runner: mockRunner) - .generateForAnnotatedElement( - MockClassElement(), defaultAnnotations, MockBuildStep()); - - expect(logs[1].message, 'Using dart environemnt'); - }); - test('flutter when wrapper is fvm', () async { - final logs = []; - logger.onRecord.listen(logs.add); - final annotations = - await loadAnnoation('next_gen_builder_fvm_test_config.dart'); - when(mockRunner.checkForFlutterEnvironemt( - wrapper: argThat( - TypeMatcher() - .having((e) => e, 'name', Wrapper.fvm), - named: 'wrapper', - ), - providedPubspecPath: anyNamed('providedPubspecPath'))) - .thenAnswer((_) async => true); - - await OpenapiGenerator(logger: logger, runner: mockRunner) - .generateForAnnotatedElement( - MockClassElement(), annotations, MockBuildStep()); - - expect(logs[1].message, 'Using flutter environemnt'); - }); - test('flutter when wrapper is ./flutter', () async { - final logs = []; - logger.onRecord.listen(logs.add); - final annotations = - await loadAnnoation('next_gen_builder_flutterw_test_config.dart'); - - when(mockRunner.checkForFlutterEnvironemt( - wrapper: argThat( - TypeMatcher() - .having((e) => e, 'name', Wrapper.flutterw), - named: 'wrapper', - ), - providedPubspecPath: anyNamed('providedPubspecPath'))) - .thenAnswer((_) async => true); - - await OpenapiGenerator(logger: logger, runner: mockRunner) - .generateForAnnotatedElement( - MockClassElement(), annotations, MockBuildStep()); - - expect(logs[1].message, 'Using flutter environemnt'); - }); - test('when defined in pubspec', () async { - final logs = []; - logger.onRecord.listen(logs.add); - final annotations = - await loadAnnoation('next_gen_builder_flutter_test_config.dart'); - - when( - mockRunner.checkForFlutterEnvironemt( - wrapper: anyNamed('wrapper'), - providedPubspecPath: argThat( - contains('./test/specs/flutter_pubspec.test.yaml'), - named: 'providedPubspecPath', - ), - ), - ).thenAnswer((_) async => true); - - await OpenapiGenerator(logger: logger, runner: mockRunner) - .generateForAnnotatedElement( - MockClassElement(), annotations, MockBuildStep()); - - expect(logs[1].message, 'Using flutter environemnt'); - }); - }); - - group('uses correct generator', () { - test('dart', () async { - final logs = []; - logger.onRecord.listen(logs.add); - - when(mockRunner.runCommand( - command: anyNamed('command'), - workingDirectory: anyNamed('workingDirectory'))) - .thenAnswer( - (_) async => ProcessResult(999, 0, 'stdout', 'stderr')); - - when(mockRunner.isSpecFileDirty( - cachedSpec: anyNamed('cachedSpec'), - loadedSpec: anyNamed('loadedSpec'))) - .thenAnswer((_) async => true); - final args = GeneratorArguments( - annotation: annotation, generator: Generator.dart); - await OpenapiGenerator(logger: logger, runner: mockRunner) - .generatorV2( - args: args, - baseCommand: 'dart', - annotatedPath: 'annotatedPath'); - final generationLogIndex = logs.indexWhere((element) => - element.message.contains( - 'Running following command to generate openapi client - ')); - logs.forEach((element) { - print(element.message); - }); - final log = logs[generationLogIndex]; - expect(log.message, contains('-g=dart')); - final sourceGenLogIndex = logs - .indexWhere((element) => element.message.contains('source gen')); - expect(logs[sourceGenLogIndex].message, - 'Skipping source gen because generator does not need it.'); - }); - test('dio', () async { - final logs = []; - logger.onRecord.listen(logs.add); - - when(mockRunner.runCommand( - command: anyNamed('command'), - workingDirectory: anyNamed('workingDirectory'))) - .thenAnswer( - (_) async => ProcessResult(999, 0, 'stdout', 'stderr')); - - when(mockRunner.isSpecFileDirty( - cachedSpec: anyNamed('cachedSpec'), - loadedSpec: anyNamed('loadedSpec'))) - .thenAnswer((_) async => true); - final args = GeneratorArguments(annotation: annotation); - await OpenapiGenerator(logger: logger, runner: mockRunner) - .generatorV2( - args: args, - baseCommand: 'dart', - annotatedPath: 'annotatedPath'); - final generationLogIndex = logs.indexWhere((element) => - element.message.contains( - 'Running following command to generate openapi client - ')); - logs.forEach((element) { - print(element.message); - }); - final log = logs[generationLogIndex]; - expect(log.message, contains('-g=dart-dio')); - }); - test('dioAlt', () async { - final logs = []; - logger.onRecord.listen(logs.add); - - when(mockRunner.runCommand( - command: anyNamed('command'), - workingDirectory: anyNamed('workingDirectory'))) - .thenAnswer( - (_) async => ProcessResult(999, 0, 'stdout', 'stderr')); - - when(mockRunner.isSpecFileDirty( - cachedSpec: anyNamed('cachedSpec'), - loadedSpec: anyNamed('loadedSpec'))) - .thenAnswer((_) async => true); - final args = GeneratorArguments( - annotation: Openapi( - inputSpecFile: - 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml', - inputSpec: RemoteSpec( - path: - 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml', - ), - generatorName: Generator.dioAlt, - useNextGen: true, - cachePath: - './test/specs/output-nextgen/expected-args/cache.json', - outputDirectory: './test/specs/output-nextgen/expected-args'), - ); - await OpenapiGenerator(logger: logger, runner: mockRunner) - .generatorV2( - args: args, - baseCommand: 'dart', - annotatedPath: 'annotatedPath'); - final generationLogIndex = logs.indexWhere((element) => - element.message.contains( - 'Running following command to generate openapi client - ')); - logs.forEach((element) { - print(element.message); - }); - final log = logs[generationLogIndex]; - expect(log.message, contains('-g=dart2-api')); - }); - }); - - group('generatorV2', () { - group('completes successfully', () { - late OpenapiGenerator generator; - setUpAll(() { - resetMockitoState(); - generator = OpenapiGenerator(logger: logger, runner: mockRunner); - when(mockRunner.runCommand( - command: anyNamed('command'), - workingDirectory: anyNamed('workingDirectory'))) - .thenAnswer((realInvocation) => - Future.value(ProcessResult(999, 0, 'success', ''))); - when(mockRunner.writeAnnotatedFile( - path: anyNamed('path'), content: anyNamed('content'))) - .thenAnswer((realInvocation) => Future.value()); - }); - - test('no diff', () async { - final logs = []; - logger.onRecord.listen(logs.add); - when(mockRunner.loadSpecFile( - specConfig: anyNamed('specConfig'), isCached: true)) - .thenAnswer((realInvocation) => Future.value({})); - when(mockRunner.loadSpecFile(specConfig: anyNamed('specConfig'))) - .thenAnswer((realInvocation) => Future.value({})); - when(mockRunner.isSpecFileDirty( - cachedSpec: anyNamed('cachedSpec'), - loadedSpec: anyNamed('loadedSpec'))) - .thenAnswer((realInvocation) => Future.value(false)); - when(mockedArgs.isRemote).thenReturn(false); - try { - await generator.generatorV2( - args: mockedArgs, - baseCommand: 'dart', - annotatedPath: 'annotatedPath'); - expect(logs.length, 4); - expect(logs[1].message, - 'No diff between versions, not running generator.'); - expect(logs[1].level, Level.INFO); - } catch (e, _) { - fail('should not have thrown.'); - } - }); - test('has diff', () async { - // setup - final logs = []; - logger.onRecord.listen(logs.add); - when(mockRunner.loadSpecFile( - specConfig: anyNamed('specConfig'), isCached: true)) - .thenAnswer((realInvocation) => Future.value({})); - when(mockRunner.loadSpecFile(specConfig: anyNamed('specConfig'))) - .thenAnswer((realInvocation) => Future.value({})); - when(mockRunner.isSpecFileDirty( - cachedSpec: anyNamed('cachedSpec'), - loadedSpec: anyNamed('loadedSpec'))) - .thenAnswer((realInvocation) => Future.value(true)); - when(mockedArgs.isRemote).thenReturn(false); - when(mockedArgs.runSourceGen).thenReturn(true); - when(mockedArgs.shouldGenerateSources).thenReturn(true); - when(mockedArgs.shouldFetchDependencies).thenReturn(true); - when(mockRunner.cacheSpecFile( - updatedSpec: anyNamed('updatedSpec'), - cachedPath: anyNamed('cachedPath'))) - .thenAnswer((_) => Future.value(VoidType)); - when(mockRunner.runCommand( - command: anyNamed('command'), - workingDirectory: anyNamed('workingDirectory'))) - .thenAnswer((realInvocation) => - Future.value(ProcessResult(999, 0, 'success', ''))); - - when(mockRunner.loadAnnotatedFile(path: anyNamed('path'))) - .thenAnswer((realInvocation) => - Future.value(['cant be empty or throws'])); - when(mockRunner.writeAnnotatedFile( - path: anyNamed('path'), content: anyNamed('content'))) - .thenAnswer((realInvocation) => Future.value(VoidType)); - - // execution - await generator.generatorV2( - args: mockedArgs, - baseCommand: 'dart', - annotatedPath: 'annotatedPath'); - expect(logs.length, 15); - }); - }); - group('logs', () { - late OpenapiGenerator generator; - setUpAll(() { - resetMockitoState(); - generator = OpenapiGenerator(logger: logger, runner: mockRunner); - }); - test('warning when using remote spec', () async { - final logs = []; - logger.onRecord.listen(logs.add); - when(mockRunner.loadSpecFile( - specConfig: anyNamed('specConfig'), isCached: true)) - .thenAnswer((realInvocation) => Future.value({})); - when(mockRunner.loadSpecFile(specConfig: anyNamed('specConfig'))) - .thenAnswer((realInvocation) => Future.value({})); - when(mockRunner.isSpecFileDirty( - cachedSpec: anyNamed('cachedSpec'), - loadedSpec: anyNamed('loadedSpec'))) - .thenAnswer((realInvocation) => Future.value(false)); - when(mockedArgs.isRemote).thenReturn(true); - when(mockRunner.runCommand( - command: anyNamed('command'), - workingDirectory: anyNamed('workingDirectory'))) - .thenAnswer((realInvocation) => - Future.value(ProcessResult(999, 0, 'success', ''))); - when(mockRunner.writeAnnotatedFile( - path: anyNamed('path'), content: anyNamed('content'))) - .thenAnswer((realInvocation) => Future.value()); - try { - await generator.generatorV2( - args: mockedArgs, - baseCommand: 'dart', - annotatedPath: 'annotatedPath'); - expect(logs.length, 6); - expect(logs[0].message, - 'Using a remote specification, a cache will still be create but may be outdated.'); - expect(logs[0].level, Level.WARNING); - } catch (e, _) { - print(e); - fail('should not have thrown.'); - } - }); - test('when no cache is found', () async { - // setup - final logs = []; - logger.onRecord.listen(logs.add); - when(mockRunner.loadSpecFile( - specConfig: anyNamed('specConfig'), isCached: true)) - .thenAnswer((realInvocation) => Future.value({})); - when(mockRunner.loadSpecFile(specConfig: anyNamed('specConfig'))) - .thenAnswer((realInvocation) => Future.value({})); - when(mockRunner.isSpecFileDirty( - cachedSpec: anyNamed('cachedSpec'), - loadedSpec: anyNamed('loadedSpec'))) - .thenAnswer((realInvocation) => Future.value(true)); - when(mockedArgs.isRemote).thenReturn(false); - when(mockedArgs.hasLocalCache).thenReturn(false); - when(mockRunner.runCommand( - command: anyNamed('command'), - workingDirectory: anyNamed('workingDirectory'))) - .thenAnswer((realInvocation) => - Future.value(ProcessResult(999, 0, 'success', ''))); - - when(mockRunner.loadAnnotatedFile(path: anyNamed('path'))) - .thenAnswer((realInvocation) => - Future.value(['cant be empty or throws'])); - when(mockRunner.writeAnnotatedFile( - path: anyNamed('path'), content: anyNamed('content'))) - .thenAnswer((realInvocation) => Future.value(VoidType)); - - // execution - await generator.generatorV2( - args: mockedArgs, - baseCommand: 'dart', - annotatedPath: 'annotatedPath'); - expect(logs.length, 15); - - final recordIndex = logs.indexWhere((element) => - element.message == 'No local cache found. Creating one.'); - expect(recordIndex, greaterThan(-1)); - expect(logs[recordIndex].level, Level.INFO); - }); - test('when cache is found', () async { - // setup - final logs = []; - logger.onRecord.listen(logs.add); - when(mockRunner.loadSpecFile( - specConfig: anyNamed('specConfig'), isCached: true)) - .thenAnswer((realInvocation) => Future.value({})); - when(mockRunner.loadSpecFile(specConfig: anyNamed('specConfig'))) - .thenAnswer((realInvocation) => Future.value({})); - when(mockRunner.isSpecFileDirty( - cachedSpec: anyNamed('cachedSpec'), - loadedSpec: anyNamed('loadedSpec'))) - .thenAnswer((realInvocation) => Future.value(true)); - when(mockedArgs.isRemote).thenReturn(false); - when(mockedArgs.hasLocalCache).thenReturn(true); - when(mockRunner.runCommand( - command: anyNamed('command'), - workingDirectory: anyNamed('workingDirectory'))) - .thenAnswer((realInvocation) => - Future.value(ProcessResult(999, 0, 'success', ''))); - - when(mockRunner.loadAnnotatedFile(path: anyNamed('path'))) - .thenAnswer((realInvocation) => - Future.value(['cant be empty or throws'])); - when(mockRunner.writeAnnotatedFile( - path: anyNamed('path'), content: anyNamed('content'))) - .thenAnswer((realInvocation) => Future.value(VoidType)); - - // execution - await generator.generatorV2( - args: mockedArgs, - baseCommand: 'dart', - annotatedPath: 'annotatedPath'); - expect(logs.length, 15); - - final recordIndex = logs.indexWhere((element) => - element.message == - 'Local cache found. Overwriting existing one.'); - expect(recordIndex, greaterThan(-1)); - expect(logs[recordIndex].level, Level.INFO); - }); - group('on failure', () { - test('has diff', () async { - // setup - final logs = []; - logger.onRecord.listen(logs.add); - when(mockRunner.loadSpecFile( - specConfig: anyNamed('specConfig'), isCached: true)) - .thenAnswer((realInvocation) => Future.value({})); - when(mockRunner.loadSpecFile(specConfig: anyNamed('specConfig'))) - .thenAnswer((realInvocation) => Future.value({})); - when(mockRunner.isSpecFileDirty( - cachedSpec: anyNamed('cachedSpec'), - loadedSpec: anyNamed('loadedSpec'))) - .thenThrow('uh oh'); - when(mockedArgs.isRemote).thenReturn(false); - when(mockedArgs.runSourceGen).thenReturn(true); - when(mockedArgs.shouldGenerateSources).thenReturn(true); - when(mockedArgs.shouldFetchDependencies).thenReturn(true); - when(mockRunner.cacheSpecFile( - updatedSpec: anyNamed('updatedSpec'), - cachedPath: anyNamed('cachedPath'))) - .thenAnswer((_) => Future.value(VoidType)); - when(mockRunner.runCommand( - command: anyNamed('command'), - workingDirectory: anyNamed('workingDirectory'))) - .thenAnswer((realInvocation) => - Future.value(ProcessResult(999, 0, 'success', ''))); - - when(mockRunner.loadAnnotatedFile(path: anyNamed('path'))) - .thenAnswer((realInvocation) => - Future.value(['cant be empty or throws'])); - when(mockRunner.writeAnnotatedFile( - path: anyNamed('path'), content: anyNamed('content'))) - .thenAnswer((realInvocation) => Future.value(VoidType)); - - // execution - await generator.generatorV2( - args: mockedArgs, - baseCommand: 'dart', - annotatedPath: 'annotatedPath'); - expect(logs.length, 5); - expect(logs[1].message, 'Failed to generate content.'); - expect(logs[1].level, Level.SEVERE); - }); - test('fails to format', () async { - // setup - final logs = []; - logger.onRecord.listen(logs.add); - when(mockRunner.loadSpecFile( - specConfig: anyNamed('specConfig'), isCached: true)) - .thenAnswer((realInvocation) => Future.value({})); - when(mockRunner.loadSpecFile(specConfig: anyNamed('specConfig'))) - .thenAnswer((realInvocation) => Future.value({})); - when(mockRunner.isSpecFileDirty( - cachedSpec: anyNamed('cachedSpec'), - loadedSpec: anyNamed('loadedSpec'))) - .thenAnswer((realInvocation) => Future.value(true)); - when(mockedArgs.isRemote).thenReturn(false); - when(mockedArgs.runSourceGen).thenReturn(true); - when(mockedArgs.shouldGenerateSources).thenReturn(true); - when(mockedArgs.shouldFetchDependencies).thenReturn(true); - when(mockRunner.cacheSpecFile( - updatedSpec: anyNamed('updatedSpec'), - cachedPath: anyNamed('cachedPath'))) - .thenAnswer((_) => Future.value(VoidType)); - when(mockedArgs.outputDirectory).thenReturn('pwd'); - when(mockRunner.runCommand( - command: anyNamed('command'), - workingDirectory: anyNamed('workingDirectory'))) - .thenAnswer((_) => - Future.value(ProcessResult(999, 0, 'success', ''))); - when(mockRunner.runCommand( - command: argThat( - TypeMatcher() - .having((c) => c.executable, 'executable', 'dart') - .having( - (c) => c.arguments, 'arguments', ['format', './']), - named: 'command', - ), - workingDirectory: - argThat(contains('pwd'), named: 'workingDirectory'), - )).thenAnswer((realInvocation) => - Future.value(ProcessResult(999, 1, '', 'err'))); - - when(mockRunner.loadAnnotatedFile(path: anyNamed('path'))) - .thenAnswer((realInvocation) => - Future.value(['cant be empty or throws'])); - when(mockRunner.writeAnnotatedFile( - path: anyNamed('path'), content: anyNamed('content'))) - .thenAnswer((realInvocation) => Future.value(VoidType)); - - // execution - await generator.generatorV2( - args: mockedArgs, - baseCommand: 'dart', - annotatedPath: 'annotatedPath'); - expect(logs.length, 15); - expect(logs[12].message, 'Failed to format generated code.'); - expect(logs[12].level, Level.SEVERE); - }); - }); - }); - }); - - group('hasDiff', () { - test('succeeds', () async { - final logs = []; - logger.onRecord.listen(logs.add); - when(mockRunner.loadSpecFile( - specConfig: anyNamed('specConfig'), isCached: true)) - .thenAnswer( - (realInvocation) => Future.value({})); - when(mockRunner.loadSpecFile(specConfig: anyNamed('specConfig'))) - .thenAnswer( - (realInvocation) => Future.value({})); - when(mockRunner.isSpecFileDirty( - cachedSpec: anyNamed('cachedSpec'), - loadedSpec: anyNamed('loadedSpec'))) - .thenAnswer((realInvocation) => Future.value(true)); - try { - expect( - await OpenapiGenerator(runner: mockRunner) - .hasDiff(args: realArguments), - isTrue); - expect(logs.length, 1); - expect(logs[0].message, 'Loaded cached and current spec files.'); - } catch (_, __) { - fail('should have completed successfully'); - } - }); - test('debug logs', () async { - final logs = []; - logger.onRecord.listen(logs.add); - when(mockedArgs.isDebug).thenReturn(true); - when(mockRunner.loadSpecFile( - specConfig: anyNamed('specConfig'), isCached: true)) - .thenAnswer( - (realInvocation) => Future.value({})); - when(mockRunner.loadSpecFile(specConfig: anyNamed('specConfig'))) - .thenAnswer( - (realInvocation) => Future.value({})); - when(mockRunner.isSpecFileDirty( - cachedSpec: anyNamed('cachedSpec'), - loadedSpec: anyNamed('loadedSpec'))) - .thenAnswer((realInvocation) => Future.value(true)); - try { - expect( - await OpenapiGenerator(runner: mockRunner) - .hasDiff(args: mockedArgs), - isTrue); - expect(logs.length, 1); - expect(logs[0].message, - 'Loaded cached and current spec files.\n{}\n{}'); - } catch (_, __) { - fail('should have completed successfully'); - } - }); - test('fails', () async { - when(mockRunner.loadSpecFile( - specConfig: anyNamed('specConfig'), isCached: true)) - .thenAnswer( - (realInvocation) => Future.value({})); - when(mockRunner.loadSpecFile(specConfig: anyNamed('specConfig'))) - .thenAnswer( - (realInvocation) => Future.value({})); - when(mockRunner.isSpecFileDirty( - cachedSpec: anyNamed('cachedSpec'), - loadedSpec: anyNamed('loadedSpec'))) - .thenThrow('uh oh'); - try { - await OpenapiGenerator(runner: mockRunner) - .hasDiff(args: realArguments); - fail('should have thrown'); - } catch (e, _) { - expect(e, isA()); - e as OutputMessage; - expect(e.message, 'Failed to check diff status.'); - expect(e.additionalContext, 'uh oh'); - expect(e.level, Level.SEVERE); - expect(e.stackTrace, isNotNull); - } - }); - }); - group('fetchDependencies', () { - test('returns successfully', () async { - when(mockedArgs.shouldFetchDependencies).thenReturn(true); - when(mockRunner.runCommand( - command: anyNamed('command'), - workingDirectory: anyNamed('workingDirectory'))) - .thenAnswer( - (_) => Future.value(ProcessResult(999, 0, 'yes', ''))); - try { - await OpenapiGenerator(runner: mockRunner) - .fetchDependencies(baseCommand: 'cmd', args: mockedArgs); - } catch (e, _) { - fail('should have completed successfully'); - } - }); - test('fails and returns an OutputMessage', () async { - when(mockedArgs.shouldFetchDependencies).thenReturn(true); - when(mockRunner.runCommand( - command: anyNamed('command'), - workingDirectory: anyNamed('workingDirectory'))) - .thenAnswer( - (_) => Future.value(ProcessResult(999, 1, '', 'uh oh'))); - try { - await OpenapiGenerator(runner: mockRunner) - .fetchDependencies(baseCommand: 'cmd', args: mockedArgs); - fail('should returned an error'); - } catch (e, _) { - expect(e, isA()); - e as OutputMessage; - expect(e.level, Level.SEVERE); - expect(e.message, 'Install within generated sources failed.'); - expect(e.additionalContext, 'uh oh'); - expect(e.stackTrace, isNotNull); - } - }); - group('logs', () { - tearDownAll(() => resetMockitoState()); - test('skips dependency fetch when flag is set', () async { - final logs = []; - logger.onRecord.listen(logs.add); - when(mockedArgs.shouldFetchDependencies).thenReturn(false); - - await OpenapiGenerator(logger: logger) - .fetchDependencies(baseCommand: 'cmd', args: mockedArgs); - - expect(logs.length, 1); - expect(logs[0].toString(), - contains('Skipping install step because flag was set.')); - expect(logs[0].level, Level.WARNING); - }); - test('debug', () async { - final logs = []; - logger.onRecord.listen(logs.add); - when(mockedArgs.shouldFetchDependencies).thenReturn(true); - when(mockedArgs.isDebug).thenReturn(true); - when(mockRunner.runCommand( - command: anyNamed('command'), - workingDirectory: anyNamed('workingDirectory'))) - .thenAnswer( - (_) => Future.value(ProcessResult(999, 0, 'yes', ''))); - - await OpenapiGenerator(logger: logger, runner: mockRunner) - .fetchDependencies(baseCommand: 'dart', args: mockedArgs); - - expect(logs.length, 2); - expect( - logs[0].message, - contains( - 'Installing dependencies with generated source. dart pub get')); - expect(logs[1].message, contains('yes')); - expect( - logs[1].message, contains('Install completed successfully.')); - expect(logs[0].level, Level.INFO); - expect(logs[1].level, Level.INFO); - }); - test('normal', () async { - final logs = []; - logger.onRecord.listen(logs.add); - when(mockedArgs.shouldFetchDependencies).thenReturn(true); - when(mockedArgs.isDebug).thenReturn(false); - when(mockRunner.runCommand( - command: anyNamed('command'), - workingDirectory: anyNamed('workingDirectory'))) - .thenAnswer( - (_) => Future.value(ProcessResult(999, 0, 'yes', ''))); - - await OpenapiGenerator(logger: logger, runner: mockRunner) - .fetchDependencies(baseCommand: 'dart', args: mockedArgs); - - expect(logs.length, 2); - expect( - logs[0].message, - contains( - 'Installing dependencies with generated source. dart pub get')); - expect(logs[1].message.contains('yes'), isFalse); - expect( - logs[1].message, contains('Install completed successfully.')); - expect(logs[0].level, Level.INFO); - expect(logs[1].level, Level.INFO); - }); - }); - }); - group('runSourceGen', () { - test('fails and returns an OutputMessage', () async { - when(mockRunner.runCommand( - command: anyNamed('command'), - workingDirectory: anyNamed('workingDirectory'))) - .thenAnswer( - (_) => Future.value(ProcessResult(999, 1, '', 'uh oh'))); - try { - await OpenapiGenerator(runner: mockRunner) - .runSourceGen(baseCommand: 'dart', args: mockedArgs); - fail('should returned an error'); - } catch (e, _) { - expect(e, isA()); - e as OutputMessage; - expect(e.level, Level.SEVERE); - expect(e.message, - 'Failed to generate source code. Build Command output:'); - expect(e.additionalContext, 'uh oh'); - expect(e.stackTrace, isNotNull); - } - }); - test('runs successfully', () async { - final logs = []; - logger.onRecord.listen(logs.add); - when(mockRunner.runCommand( - command: anyNamed('command'), - workingDirectory: anyNamed('workingDirectory'))) - .thenAnswer((_) => Future.value(ProcessResult(999, 0, '', ''))); - try { - await OpenapiGenerator(runner: mockRunner) - .runSourceGen(baseCommand: 'dart', args: mockedArgs); - expect(logs.length, 3); - expect(logs[0].message, 'Running source code generation.'); - expect(logs[1].message, - 'dart pub run build_runner build --delete-conflicting-outputs'); - expect(logs[2].message, 'Codegen completed successfully.'); - for (final log in logs) { - expect(log.level, Level.INFO); - } - } catch (e, _) { - fail('should have completed normally'); - } - }); - }); - group('generateSources', () { - test('skips when flag is set', () async { - final logs = []; - logger.onRecord.listen(logs.add); - when(mockedArgs.runSourceGen).thenReturn(false); - try { - await OpenapiGenerator(runner: mockRunner) - .generateSources(baseCommand: 'dart', args: mockedArgs); - expect(logs.length, 1); - expect(logs[0].message, - 'Skipping source gen step due to flag being set.'); - expect(logs[0].level, Level.WARNING); - } catch (e, _) { - fail('should have completed normally'); - } - }); - test('skips when not needed', () async { - final logs = []; - logger.onRecord.listen(logs.add); - when(mockedArgs.runSourceGen).thenReturn(true); - when(mockedArgs.shouldGenerateSources).thenReturn(false); - try { - await OpenapiGenerator(runner: mockRunner) - .generateSources(baseCommand: 'dart', args: mockedArgs); - expect(logs.length, 1); - expect(logs[0].message, - 'Skipping source gen because generator does not need it.'); - expect(logs[0].level, Level.INFO); - } catch (e, _) { - fail('should have completed normally'); - } - }); - test('completes successfully', () async { - final logs = []; - logger.onRecord.listen(logs.add); - when(mockedArgs.runSourceGen).thenReturn(true); - when(mockedArgs.shouldGenerateSources).thenReturn(true); - when(mockRunner.runCommand( - command: anyNamed('command'), - workingDirectory: anyNamed('workingDirectory'))) - .thenAnswer((_) => Future.value(ProcessResult(999, 0, '', ''))); - try { - await OpenapiGenerator(runner: mockRunner) - .generateSources(baseCommand: 'dart', args: mockedArgs); - expect(logs.length, 4); - expect(logs[3].message, 'Sources generated successfully.'); - expect(logs[3].level, Level.INFO); - } catch (e, _) { - fail('should have completed normally'); - } - }); - test('fails', () async { - final logs = []; - logger.onRecord.listen(logs.add); - when(mockedArgs.runSourceGen).thenReturn(true); - when(mockedArgs.shouldGenerateSources).thenReturn(true); - when(mockRunner.runCommand( - command: anyNamed('command'), - workingDirectory: anyNamed('workingDirectory'))) - .thenAnswer( - (_) => Future.value(ProcessResult(999, 1, '', 'uh oh'))); - try { - await OpenapiGenerator(runner: mockRunner) - .generateSources(baseCommand: 'dart', args: mockedArgs); - fail('should have failed'); - } catch (e, _) { - expect(e, isA()); - e as OutputMessage; - expect(e.message, 'Could not complete source generation'); - expect(e.level, Level.SEVERE); - expect(e.additionalContext, isA()); - expect(e.stackTrace, isNotNull); - } - }); - }); - group('updateAnnotatedFile', () { - test('fails', () async { - when(mockRunner.loadAnnotatedFile(path: 'annotatedPath')) - .thenAnswer((realInvocation) => Future.error('uh')); - try { - await OpenapiGenerator(runner: mockRunner) - .updateAnnotatedFile(annotatedPath: 'annotatedPath'); - fail('should have thrown an error'); - } catch (e, _) { - expect(e, isA()); - e as OutputMessage; - expect(e.message, 'Failed to update the annotated class file.'); - expect(e.level, Level.SEVERE); - expect(e.additionalContext, 'uh'); - expect(e.stackTrace, isNotNull); - } - }); - test('finds timestamp', () async { - final logs = []; - logger.onRecord.listen(logs.add); - when(mockRunner.loadAnnotatedFile(path: 'annotatedPath')).thenAnswer( - (realInvocation) => - Future.value(['$lastRunPlaceHolder: something', 'more'])); - when(mockRunner.writeAnnotatedFile( - path: 'annotatedPath', content: anyNamed('content'))) - .thenAnswer((realInvocation) => Future.value()); - try { - await OpenapiGenerator(runner: mockRunner) - .updateAnnotatedFile(annotatedPath: 'annotatedPath'); - expect(logs.length, 1); - expect(logs[0].message, - contains('Found generated timestamp. Updating with ')); - } catch (_, __) { - fail('should have completed successfully'); - } - }); - test('does not find timestamp', () async { - final logs = []; - logger.onRecord.listen(logs.add); - when(mockRunner.loadAnnotatedFile(path: 'annotatedPath')) - .thenAnswer((realInvocation) => Future.value(['more'])); - when(mockRunner.writeAnnotatedFile( - path: 'annotatedPath', content: anyNamed('content'))) - .thenAnswer((realInvocation) => Future.value()); - try { - await OpenapiGenerator(runner: mockRunner) - .updateAnnotatedFile(annotatedPath: 'annotatedPath'); - expect(logs.length, 1); - expect(logs[0].message, - contains('Creating generated timestamp with ')); - } catch (_, __) { - fail('should have completed successfully'); - } - }); - }); - group('formatCode', () { - test('logs on success', () async { - final logs = []; - logger.onRecord.listen(logs.add); - when(mockRunner.runCommand( - command: anyNamed('command'), - workingDirectory: anyNamed('workingDirectory'))) - .thenAnswer((_) => Future.value(ProcessResult(999, 0, '', ''))); - try { - await OpenapiGenerator(runner: mockRunner, logger: logger) - .formatCode(args: mockedArgs); - expect(logs.length, 1); - expect(logs[0].message, 'Successfully formatted code.'); - expect(logs[0].level, Level.INFO); - } catch (e, _) { - fail('should complete successfully'); - } - }); - test('fails and returns an OutputMessage', () async { - when(mockRunner.runCommand( - command: anyNamed('command'), - workingDirectory: anyNamed('workingDirectory'))) - .thenAnswer( - (_) => Future.value(ProcessResult(999, 1, '', 'uh oh'))); - try { - await OpenapiGenerator(runner: mockRunner) - .formatCode(args: mockedArgs); - fail('should returned an error'); - } catch (e, _) { - expect(e, isA()); - e as OutputMessage; - expect(e.level, Level.SEVERE); - expect(e.message, 'Failed to format generated code.'); - expect(e.additionalContext, 'uh oh'); - expect(e.stackTrace, isNotNull); - } - }); - }); - group('runOpenApiJar', () { - group('logs', () { - tearDownAll(() => resetMockitoState()); - test('normal', () async { - final logs = []; - logger.onRecord.listen(logs.add); - when( - mockRunner.runCommand( - command: anyNamed('command'), - workingDirectory: anyNamed( - 'workingDirectory', - ), - ), - ).thenAnswer( - (realInvocation) => Future.value( - ProcessResult(999, 0, 'jar successful', ''), - ), - ); - - when(mockedArgs.jarArgs) - .thenAnswer((realInvocation) => realArguments.jarArgs); - when(mockedArgs.isDebug).thenReturn(false); - await OpenapiGenerator(runner: mockRunner, logger: logger) - .runOpenApiJar(arguments: mockedArgs); - expect(logs.length, 2); - expect( - logs[0].message, - contains( - 'Running following command to generate openapi client - [ ${(await realArguments.jarArgs).join(' ')} ]')); - expect(logs[1].message.contains('jar successful'), isFalse); - expect(logs[1].message, - contains('Openapi generator completed successfully.')); - for (final log in logs) { - expect(log.level, Level.INFO); - } - }); - - test('debug', () async { - final logs = []; - logger.onRecord.listen(logs.add); - when( - mockRunner.runCommand( - command: anyNamed('command'), - workingDirectory: anyNamed( - 'workingDirectory', - ), - ), - ).thenAnswer( - (realInvocation) => Future.value( - ProcessResult(999, 0, 'jar successful', ''), - ), - ); - - when(mockedArgs.jarArgs) - .thenAnswer((realInvocation) => realArguments.jarArgs); - when(mockedArgs.isDebug).thenReturn(true); - await OpenapiGenerator(runner: mockRunner, logger: logger) - .runOpenApiJar(arguments: mockedArgs); - expect(logs.length, 2); - expect( - logs[0].message, - contains( - 'Running following command to generate openapi client - [ ${(await realArguments.jarArgs).join(' ')} ]')); - expect(logs[1].message, contains('jar successful')); - expect(logs[1].message, - contains('Openapi generator completed successfully.')); - for (final log in logs) { - expect(log.level, Level.INFO); - } - }); - }); - test('returns successfully', () async { - when( - mockRunner.runCommand( - command: anyNamed('command'), - workingDirectory: anyNamed( - 'workingDirectory', - ), - ), - ).thenAnswer( - (realInvocation) => Future.value( - ProcessResult(999, 0, 'completed successfully', ''), - ), - ); - try { - await OpenapiGenerator(runner: mockRunner) - .runOpenApiJar(arguments: realArguments); - } catch (e, _) { - fail('should have completed successfully.'); - } - }); - test('returns an error when the jar command fails', () async { - when( - mockRunner.runCommand( - command: anyNamed('command'), - workingDirectory: anyNamed( - 'workingDirectory', - ), - ), - ).thenAnswer( - (realInvocation) => Future.value( - ProcessResult(999, 1, '', 'something went wrong'), - ), - ); - - try { - await OpenapiGenerator(runner: mockRunner) - .runOpenApiJar(arguments: realArguments); - fail( - 'should have returned an error log.', - ); - } catch (e, _) { - expect(e, isA()); - e as OutputMessage; - expect(e.level, Level.SEVERE); - expect(e.message, 'Codegen Failed. Generator output:'); - expect(e.additionalContext, 'something went wrong'); - expect(e.stackTrace, isNotNull); - } - }); - }); - }); - }); -} diff --git a/openapi-generator/test/mocks.dart b/openapi-generator/test/mocks.dart deleted file mode 100644 index d167d09..0000000 --- a/openapi-generator/test/mocks.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'dart:io'; - -import 'package:analyzer/dart/element/element.dart'; -import 'package:build/build.dart'; -import 'package:mockito/annotations.dart'; -import 'package:openapi_generator/src/models/command.dart'; -import 'package:openapi_generator/src/models/generator_arguments.dart'; -import 'package:openapi_generator/src/openapi_generator_runner.dart'; -import 'package:openapi_generator_annotations/openapi_generator_annotations.dart'; - -@GenerateNiceMocks([ - MockSpec(), - MockSpec(), - MockSpec(), - MockSpec(), - MockSpec(), - MockSpec(), - MockSpec(), - MockSpec() -]) -void main() {} diff --git a/openapi-generator/test/specs/buckets.sh b/openapi-generator/test/specs/buckets.sh new file mode 100755 index 0000000..17ef658 --- /dev/null +++ b/openapi-generator/test/specs/buckets.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# A simple bash script that sets up the local stack testing environment for AWS Remote specs +awslocal s3 mb s3://bucket +SPEC="$(curl -XGET https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml)" +echo "$SPEC" >> oasspec.yaml +awslocal s3api put-object --bucket bucket --key openapi.yaml --body oasspec.yaml \ No newline at end of file diff --git a/openapi-generator/test/specs/next_gen_builder_flutter_test_config.dart b/openapi-generator/test/specs/next_gen_builder_flutter_test_config.dart index 8f2f3c8..b06211f 100644 --- a/openapi-generator/test/specs/next_gen_builder_flutter_test_config.dart +++ b/openapi-generator/test/specs/next_gen_builder_flutter_test_config.dart @@ -10,4 +10,4 @@ import 'package:openapi_generator_annotations/openapi_generator_annotations.dart cachePath: './test/specs/managed-cache.json', projectPubspecPath: './test/specs/flutter_pubspec.test.yaml', ) -class TestClassConfig {} +class TestClassConfig extends OpenapiGeneratorConfig {} diff --git a/openapi-generator/test/specs/next_gen_builder_flutterw_test_config.dart b/openapi-generator/test/specs/next_gen_builder_flutterw_test_config.dart deleted file mode 100644 index f71442c..0000000 --- a/openapi-generator/test/specs/next_gen_builder_flutterw_test_config.dart +++ /dev/null @@ -1,17 +0,0 @@ -library test_lib; - -import 'package:openapi_generator_annotations/openapi_generator_annotations.dart'; - -@Openapi( - inputSpecFile: - 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml', - inputSpec: RemoteSpec( - path: - 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml', - ), - generatorName: Generator.dio, - useNextGen: true, - additionalProperties: AdditionalProperties(wrapper: Wrapper.flutterw), - cachePath: './test/specs/output-nextgen/expected-args/cache.json', - outputDirectory: './test/specs/output-nextgen/expected-args') -class TestClassConfig {} diff --git a/openapi-generator/test/specs/next_gen_builder_fvm_test_config.dart b/openapi-generator/test/specs/next_gen_builder_test_aws_config.dart similarity index 61% rename from openapi-generator/test/specs/next_gen_builder_fvm_test_config.dart rename to openapi-generator/test/specs/next_gen_builder_test_aws_config.dart index 69e4b5c..5fd3f12 100644 --- a/openapi-generator/test/specs/next_gen_builder_fvm_test_config.dart +++ b/openapi-generator/test/specs/next_gen_builder_test_aws_config.dart @@ -7,13 +7,15 @@ import 'package:openapi_generator_annotations/openapi_generator_annotations.dart 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml', inputSpec: RemoteSpec( path: - 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml', + 'http://bucket.s3.us-east-1.localhost.localstack.cloud:4566/openapi.yaml', + headerDelegate: AWSRemoteSpecHeaderDelegate( + bucket: 'bucket', + accessKeyId: 'test', + secretAccessKey: 'test', + ), ), generatorName: Generator.dio, - additionalProperties: AdditionalProperties( - wrapper: Wrapper.fvm, - ), useNextGen: true, cachePath: './test/specs/output-nextgen/expected-args/cache.json', outputDirectory: './test/specs/output-nextgen/expected-args') -class TestClassConfig {} +class TestClassConfig extends OpenapiGeneratorConfig {} diff --git a/openapi-generator/test/specs/next_gen_builder_test_config.dart b/openapi-generator/test/specs/next_gen_builder_test_config.dart index 859f19e..d9f36c3 100644 --- a/openapi-generator/test/specs/next_gen_builder_test_config.dart +++ b/openapi-generator/test/specs/next_gen_builder_test_config.dart @@ -13,4 +13,4 @@ import 'package:openapi_generator_annotations/openapi_generator_annotations.dart useNextGen: true, cachePath: './test/specs/output-nextgen/expected-args/cache.json', outputDirectory: './test/specs/output-nextgen/expected-args') -class TestClassConfig {} +class TestClassConfig extends OpenapiGeneratorConfig {} diff --git a/openapi-generator/test/specs/next_gen_builder_test_invalid_config.dart b/openapi-generator/test/specs/next_gen_builder_test_invalid_config.dart deleted file mode 100644 index a19474d..0000000 --- a/openapi-generator/test/specs/next_gen_builder_test_invalid_config.dart +++ /dev/null @@ -1,16 +0,0 @@ -library test_lib; - -import 'package:openapi_generator_annotations/openapi_generator_annotations.dart'; - -@Openapi( - inputSpecFile: - 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml', - inputSpec: RemoteSpec( - path: - 'https://raw.githubusercontent.com/Nexushunter/tagmine-api/main/openapi.yaml', - ), - generatorName: Generator.dio, - useNextGen: false, - cachePath: './test/specs/output-nextgen/expected-args/cache.json', - outputDirectory: './test/specs/output-nextgen/expected-args') -class TestClassConfig {} diff --git a/openapi-generator/test/test_annotations/test_configs.dart b/openapi-generator/test/test_annotations/test_configs.dart new file mode 100644 index 0000000..92622ac --- /dev/null +++ b/openapi-generator/test/test_annotations/test_configs.dart @@ -0,0 +1,77 @@ +library test_annotations; + +import 'package:openapi_generator_annotations/openapi_generator_annotations.dart'; +import 'package:source_gen_test/annotations.dart'; + +@ShouldGenerate(r''' +const alwaysRun = false; + +const fetchDependencies = true; + +const generatorName = 'dio'; + +const inputSpecFile = ''; + +const runSourceGenOnOutput = true; + +const skipSpecValidation = false; + +const useNextGen = false; +''') +@Openapi(inputSpecFile: '', generatorName: Generator.dio) +class TestClassDefault extends OpenapiGeneratorConfig {} + +@ShouldThrow('useNextGen must be set when using cachePath', element: false) +@Openapi(inputSpecFile: '', generatorName: Generator.dio, cachePath: './') +class TestClassInvalidCachePathUsage extends OpenapiGeneratorConfig {} + +@ShouldGenerate(r''' +const additionalProperties = wrapper = 'flutterw'; + +const alwaysRun = false; + +const fetchDependencies = true; + +const generatorName = 'dart'; + +const inputSpecFile = ''; + +const runSourceGenOnOutput = true; + +const skipSpecValidation = false; + +const useNextGen = false; +''') +@Openapi( + inputSpecFile: '', + generatorName: Generator.dart, + additionalProperties: AdditionalProperties(wrapper: Wrapper.flutterw), +) +class TestClassHasCustomAnnotations extends OpenapiGeneratorConfig {} + +@ShouldGenerate(r''' +const additionalProperties = wrapper = 'flutterw', nullableFields = 'true'; + +const alwaysRun = false; + +const fetchDependencies = true; + +const generatorName = 'dart'; + +const inputSpecFile = ''; + +const runSourceGenOnOutput = true; + +const skipSpecValidation = false; + +const useNextGen = false; +''') +@Openapi( + inputSpecFile: '', + generatorName: Generator.dart, + additionalProperties: DioProperties( + wrapper: Wrapper.flutterw, + nullableFields: true, + ), +) +class TestClassHasDioProperties extends OpenapiGeneratorConfig {} diff --git a/openapi-generator/test/test_annotations/test_generator.dart b/openapi-generator/test/test_annotations/test_generator.dart new file mode 100644 index 0000000..1bee080 --- /dev/null +++ b/openapi-generator/test/test_annotations/test_generator.dart @@ -0,0 +1,113 @@ +import 'package:analyzer/dart/element/element.dart'; +import 'package:build/src/builder/build_step.dart'; +import 'package:openapi_generator/src/utils.dart'; +import 'package:openapi_generator_annotations/openapi_generator_annotations.dart'; +import 'package:source_gen/source_gen.dart' as src_gen; + +class TestGenerator extends src_gen.GeneratorForAnnotation { + final bool requireTestClassPrefix; + + const TestGenerator({this.requireTestClassPrefix = true}); + + @override + Iterable generateForAnnotatedElement(Element element, + src_gen.ConstantReader annotation, BuildStep buildStep) sync* { + assert(!annotation.isNull, 'The source generator should\'nt be null'); + + if (element is! ClassElement) { + throw src_gen.InvalidGenerationSourceError( + 'Only supports annotated classes.', + todo: 'Remove `TestAnnotation` from the associated element.', + element: element, + ); + } + + if (requireTestClassPrefix && !element.name.startsWith('TestClass')) { + throw src_gen.InvalidGenerationSourceError( + 'All classes must start with `TestClass`.', + todo: 'Rename the type or remove the `TestAnnotation` from class.', + element: element, + ); + } + + if (!(annotation.read('useNextGen').literalValue as bool)) { + if (annotation.read('cachePath').literalValue != null) { + throw src_gen.InvalidGenerationSourceError( + 'useNextGen must be set when using cachePath'); + } + } + + // KEEP THIS IN LINE WITH THE FIELDS OF THE ANNOTATION CLASS + final fields = [ + SupportedFields(name: 'additionalProperties', type: AdditionalProperties), + SupportedFields( + name: 'overwriteExistingFiles', isDeprecated: true, type: bool), + SupportedFields(name: 'skipSpecValidation', type: bool), + SupportedFields(name: 'inputSpecFile', isRequired: true, type: String), + SupportedFields(name: 'templateDirectory', type: String), + SupportedFields(name: 'generatorName', isRequired: true, type: Generator), + SupportedFields(name: 'outputDirectory', type: Map), + SupportedFields(name: 'typeMappings', type: Map), + SupportedFields(name: 'importMappings', type: Map), + SupportedFields(name: 'reservedWordsMappings', type: Map), + SupportedFields(name: 'inlineSchemaNameMappings', type: Map), + // SupportedFields(name:'inlineSchemaOptions'), + SupportedFields(name: 'apiPackage', type: String), + SupportedFields(name: 'fetchDependencies', type: bool), + SupportedFields(name: 'runSourceGenOnOutput', type: bool), + SupportedFields(name: 'alwaysRun', isDeprecated: true, type: bool), + SupportedFields(name: 'cachePath', type: String), + SupportedFields(name: 'useNextGen', type: bool), + SupportedFields(name: 'projectPubspecPath', type: String), + ]..sort((a, b) => a.name.compareTo(b.name)); + for (final field in fields) { + final v = annotation.read(field.name); + try { + if ([ + 'inputSpecFile', + 'projectPubspecPath', + 'apiPackage', + 'templateDirectory', + 'generatorName' + ].any((element) => field.name == element)) { + yield 'const ${field.name}=\'${convertToPropertyValue(v.objectValue)}\';\n'; + } else if (field.name == 'additionalProperties') { + final mapping = v.revive().namedArguments.map( + (key, value) => MapEntry(key, convertToPropertyValue(value))); + // TODO: Is this the expected behaviour? + // Iterable> entries; + // if (v.objectValue.type is DioProperties) { + // entries = DioProperties.fromMap(mapping).toMap().entries; + // } else if (v.objectValue.type is DioAltProperties) { + // entries = DioAltProperties.fromMap(mapping).toMap().entries; + // } else { + // entries = AdditionalProperties.fromMap(mapping).toMap().entries; + // } + yield 'const ${field.name}=${mapping.entries.fold('', foldStringMap(valueModifier: (value) => '\'$value\''))};'; + } else { + yield 'const ${field.name}=${convertToPropertyValue(v.objectValue)};\n'; + } + } catch (_, __) { + continue; + } + } + } + + @override + String toString() => + 'TestGenerator (requireTestClassPrefix:$requireTestClassPrefix)'; +} + +class SupportedFields { + final String name; + final bool isRequired; + final bool isDeprecated; + final T? type; + + const SupportedFields({ + required this.name, + this.isDeprecated = false, + this.isRequired = false, + required this.type, + }); +} diff --git a/openapi-generator/test/utils.dart b/openapi-generator/test/utils.dart index 97bdb38..eea873a 100644 --- a/openapi-generator/test/utils.dart +++ b/openapi-generator/test/utils.dart @@ -1,17 +1,54 @@ import 'dart:io'; +import 'package:build/build.dart'; import 'package:build_test/build_test.dart'; +import 'package:openapi_generator/src/models/output_message.dart'; +import 'package:openapi_generator/src/openapi_generator_runner.dart'; import 'package:source_gen/source_gen.dart'; +final String pkgName = 'pkg'; + +final Builder builder = LibraryBuilder(OpenapiGenerator(testMode: true), + generatedExtension: '.openapi_generator'); final testSpecPath = '${Directory.current.path}${Platform.pathSeparator}test${Platform.pathSeparator}specs${Platform.pathSeparator}'; -Future loadAnnoation(String testConfigPath) async => - (await resolveSource( - File('$testSpecPath/$testConfigPath').readAsStringSync(), - (resolver) async => - (await resolver.findLibraryByName('test_lib'))!)) - .getClass('TestClassConfig')! - .metadata - .map((e) => ConstantReader(e.computeConstantValue()!)) - .first; +/// Runs an in memory test variant of the generator with the given [source]. +/// +/// [path] available so an override for the adds generated comment test can +/// compare the output. +Future generate(String source, {String path = 'lib/myapp.dart'}) async { + final spec = File('${testSpecPath}openapi.test.yaml').readAsStringSync(); + var srcs = { + 'openapi_generator_annotations|lib/src/openapi_generator_annotations_base.dart': + File('../openapi-generator-annotations/lib/src/openapi_generator_annotations_base.dart') + .readAsStringSync(), + 'openapi_generator|$path': ''' + import 'package:openapi_generator_annotations/src/openapi_generator_annotations_base.dart'; + $source + class MyApp { + } + ''', + 'openapi_generator|openapi-spec.yaml': spec + }; + + // Capture any message from generation; if there is one, return that instead of + // the generated output. + String? logMessage; + void captureLog(dynamic logRecord) { + if (logRecord is OutputMessage) { + logMessage = + '${logMessage ?? ''}\n${logRecord.level} ${logRecord.message} \n ${logRecord.additionalContext} \n ${logRecord.stackTrace}'; + } else { + logMessage = + '${logMessage ?? ''}\n${logRecord.message ?? ''}\n${logRecord.error ?? ''}\n${logRecord.stackTrace ?? ''}'; + } + } + + var writer = InMemoryAssetWriter(); + await testBuilder(builder, srcs, + rootPackage: pkgName, writer: writer, onLog: captureLog); + return logMessage ?? + String.fromCharCodes( + writer.assets[AssetId(pkgName, 'lib/value.g.dart')] ?? []); +} diff --git a/openapi-generator/test/verify_generation_test.dart b/openapi-generator/test/verify_generation_test.dart new file mode 100644 index 0000000..e1dec00 --- /dev/null +++ b/openapi-generator/test/verify_generation_test.dart @@ -0,0 +1,17 @@ +import 'dart:io'; + +import 'package:openapi_generator_annotations/openapi_generator_annotations.dart'; +import 'package:source_gen_test/source_gen_test.dart'; + +import 'test_annotations/test_generator.dart'; + +void main() async { + final reader = await initializeLibraryReaderForDirectory( + '${Directory.current.path}${Platform.pathSeparator}test${Platform.pathSeparator}test_annotations', + 'test_configs.dart', + ); + + initializeBuildLogTracking(); + + testAnnotatedElements(reader, const TestGenerator()); +}