From a95e811923d151b2227721306fabc2d20087d039 Mon Sep 17 00:00:00 2001 From: Eduardo Folly Date: Sun, 24 May 2026 15:20:03 -0300 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=8F=97=EF=B8=8F=20Update=20project=20?= =?UTF-8?q?structure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main.yml | 134 +++++++++++++++------- .github/workflows/test.yml | 21 ++-- .gitignore | 1 + CHANGELOG.md | 8 +- README.md | 9 -- bin/check.dart | 189 +------------------------------- check.iml | 14 --- helpers/create_index.dart | 104 +++++++++++++++--- helpers/package.dart | 23 ---- helpers/pix.png | Bin 24597 -> 0 bytes helpers/update_version.dart | 24 ++++ lib/main.dart | 187 +++++++++++++++++++++++++++++++ {bin => lib}/returned_data.dart | 0 pubspec.yaml | 11 +- test/check_local_test.dart | 42 +------ 15 files changed, 420 insertions(+), 347 deletions(-) delete mode 100644 check.iml delete mode 100644 helpers/package.dart delete mode 100644 helpers/pix.png create mode 100644 helpers/update_version.dart create mode 100644 lib/main.dart rename {bin => lib}/returned_data.dart (100%) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 03ca35a..6c03a0a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,74 +4,61 @@ on: branches: [ main ] jobs: - build: - name: Tests, Build & Deploy + prepare: + name: Prepare Release runs-on: ubuntu-latest - timeout-minutes: 20 + timeout-minutes: 10 permissions: contents: write + outputs: + VERSION: ${{ steps.get_version.outputs.VERSION }} steps: - name: Code Checkout uses: actions/checkout@v6 - name: Get Pubspec Version + id: get_version run: | VERSION=$(grep 'version:' pubspec.yaml | cut -c 10- | cut -f 1 -d '+') - sed -i "s/dev/$VERSION/g" bin/check.dart - echo "VERSION=$VERSION" >> $GITHUB_ENV + echo "VERSION=$VERSION" >> $GITHUB_OUTPUT - name: Check if version is used run: | - URL=$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/releases/tags/${{ env.VERSION }} + URL=$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/releases/tags/${{ steps.get_version.outputs.VERSION }} CODE=$(curl -s -H "Authorization: Bearer ${{ github.token }}" -o /dev/null -w "%{http_code}" "$URL") if [ "$CODE" != 404 ]; then - echo "Release '$VERSION' already exists. ($CODE)" + echo "Release 'v${{ steps.get_version.outputs.VERSION }}' already exists. ($CODE)" exit 1 fi - - name: Flutter Environment - uses: subosito/flutter-action@v2 + - name: Dart Environment + uses: dart-lang/setup-dart@v1 with: - channel: 'stable' - cache: false + sdk: stable + + - name: Dart Pub Get + run: dart pub get + + - name: Dart Analyze + run: dart analyze --fatal-infos - name: Install lcov env: DEBIAN_FRONTEND: noninteractive run: | - sudo apt-get update -y + sudo apt update -y sudo apt install -y lcov - - name: Dart Pub Get - timeout-minutes: 3 - run: dart pub get - - name: Dart Test run: | dart test \ - --coverage=coverage \ + --coverage-path=./coverage/lcov.info \ + --reporter=github \ --concurrency=$(grep -c processor /proc/cpuinfo) - - name: Dart Compile - run: | - mkdir -p build - dart compile exe bin/check.dart -o build/check - - - name: Creating package filter - run: dart run helpers/package.dart - - - name: Creating lcov.info - run: | - dart run coverage:format_coverage \ - --packages=coverage/package.json \ - --lcov \ - -i coverage \ - -o coverage/lcov.info - - name: Creating Test Coverage HTML run: | genhtml coverage/lcov.info \ - --ignore-errors empty \ --output-directory coverage/html/coverage \ --title "check" \ --show-details @@ -89,14 +76,85 @@ jobs: - name: Creating a GitHub Tag uses: mathieudutour/github-tag-action@v6.2 with: - custom_tag: ${{ env.VERSION }} - tag_prefix: '' + custom_tag: ${{ steps.get_version.outputs.VERSION }} github_token: ${{ secrets.GITHUB_TOKEN }} + build: + name: Build ${{ matrix.target }} + needs: prepare + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - runner: ubuntu-latest + target: linux-amd64 + ext: "" + - runner: ubuntu-24.04-arm + target: linux-arm64 + ext: "" + - runner: macos-15-intel + target: macos-intel + ext: "" + - runner: macos-15 + target: macos-silicon + ext: "" + - runner: windows-latest + target: windows-amd64 + ext: ".exe" + + steps: + - name: Code Checkout + uses: actions/checkout@v6 + + - name: Dart Environment + uses: dart-lang/setup-dart@v1 + with: + sdk: stable + + - name: Dart Pub Get + shell: bash + run: dart pub get + + - name: Dart Test + shell: bash + run: dart test + + - name: Update Version in Code + shell: bash + run: dart run helpers/update_version.dart ${{ needs.prepare.outputs.VERSION }} + + - name: Dart Compile + shell: bash + run: | + mkdir -p build + dart compile exe bin/check.dart -o build/check-${{ matrix.target }}${{ matrix.ext }} + + - name: Upload Artifact + uses: actions/upload-artifact@v7 + with: + name: check-${{ matrix.target }} + path: build/check-${{ matrix.target }}${{ matrix.ext }} + retention-days: 1 + + release: + name: Create GitHub Release + needs: [ prepare, build ] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Download All Artifacts + uses: actions/download-artifact@v8 + with: + path: artifacts + merge-multiple: true + - name: Create a GitHub Release uses: ncipollo/release-action@v1 with: - tag: ${{ env.VERSION }} + tag: ${{ needs.prepare.outputs.VERSION }} token: ${{ secrets.GITHUB_TOKEN }} allowUpdates: true - artifacts: build/* + artifacts: artifacts/* + generateReleaseNotes: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b2ff89d..4bc98cb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,29 +6,24 @@ jobs: tests: name: Tests runs-on: ubuntu-latest - timeout-minutes: 20 + timeout-minutes: 10 steps: - name: Code Checkout uses: actions/checkout@v6 - - name: Get Pubspec Version - run: | - VERSION=$(grep 'version:' pubspec.yaml | cut -c 10- | cut -f 1 -d '+') - echo "VERSION=$VERSION" >> $GITHUB_ENV - - - name: Flutter Environment - uses: subosito/flutter-action@v2 + - name: Dart Environment + uses: dart-lang/setup-dart@v1 with: - channel: 'stable' - cache: false + sdk: stable - name: Dart Pub Get - timeout-minutes: 3 run: dart pub get - name: Dart Analyze - timeout-minutes: 3 run: dart analyze --fatal-infos - name: Dart Test - run: dart test + run: | + dart test \ + --reporter=github \ + --concurrency=$(grep -c processor /proc/cpuinfo) diff --git a/.gitignore b/.gitignore index bc608cf..3e34161 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # Created by `dart pub` .dart_tool/ .idea/ +coverage/ .env pubspec.lock check diff --git a/CHANGELOG.md b/CHANGELOG.md index 8efa042..664490a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,10 @@ -## 0.0.4 [2026-01-11] +## 0.0.5 - 2026-05-24 + +- Multiple architecture build. +- Fix code coverage. +- Updating project plugins, CI and dependencies. + +## 0.0.4 - 2026-01-11 - Updating project plugins, CI and dependencies. diff --git a/README.md b/README.md index e5a0959..a7d074c 100644 --- a/README.md +++ b/README.md @@ -26,15 +26,6 @@ Thank you for your continued support! [![BuyMeACoffee](https://www.buymeacoffee.com/assets/img/guidelines/download-assets-sm-2.svg)](https://www.buymeacoffee.com/edufolly) -## PIX - -Sua contribuição ajudará a impulsionar o desenvolvimento de ferramentas de -qualidade para a comunidade de desenvolvedores Flutter e Dart. Qualquer quantia -será apreciada. -Obrigado pelo seu apoio contínuo! - -[![PIX](helpers/pix.png)](https://nubank.com.br/pagar/2bt2q/RBr4Szfuwr) - ## Download ```shell diff --git a/bin/check.dart b/bin/check.dart index 4fa6e31..0b66fdf 100644 --- a/bin/check.dart +++ b/bin/check.dart @@ -1,13 +1,7 @@ import 'dart:io'; -import 'package:args/args.dart'; -import 'package:http/http.dart'; - -import 'returned_data.dart'; - -const String version = 'dev'; - -enum Method { head, get, post, put, patch, delete } +import 'package:check/main.dart'; +import 'package:check/returned_data.dart'; void main(List arguments) async { final ReturnedData data = await check(arguments); @@ -22,182 +16,3 @@ void main(List arguments) async { exit(0); } - -ArgParser buildParser() { - return ArgParser() - ..addFlag( - 'help', - abbr: 'h', - negatable: false, - help: 'Print this usage information.', - ) - ..addOption( - 'timeout', - abbr: 't', - help: 'Set the timeout for the request.', - defaultsTo: '10', - valueHelp: 'in seconds', - ) - ..addOption( - 'content-type', - help: 'Set the content type for the request.', - defaultsTo: 'application/json', - valueHelp: 'type', - ) - ..addOption( - 'user-agent', - help: 'Set the user agent for the request.', - defaultsTo: 'check/$version', - valueHelp: 'agent', - ) - ..addFlag('fail', help: 'Fail on non-200 status code.', defaultsTo: true) - ..addFlag( - 'verbose', - abbr: 'v', - negatable: false, - help: 'Show additional command output.', - ) - ..addFlag('version', negatable: false, help: 'Print the tool version.'); -} - -void printUsage(ArgParser argParser) { - print('Usage: check [METHOD] URL'); - print(argParser.usage); -} - -Future check(List arguments) async { - final ArgParser argParser = buildParser(); - try { - final ArgResults results = argParser.parse(arguments); - final verbose = results.wasParsed('verbose'); - Method method = Method.get; - int urlPos = 0; - Duration timeout = const Duration(seconds: 10); - - if (results.wasParsed('help')) { - printUsage(argParser); - return ReturnedData.empty(); - } - - if (results.wasParsed('version')) { - print('check version: $version'); - return ReturnedData.empty(); - } - - if (results.wasParsed('timeout')) { - try { - final int value = int.parse(results['timeout']); - if (value < 1) { - throw FormatException('Invalid timeout value: ${results['timeout']}'); - } - timeout = Duration(seconds: value); - } on Exception { - throw FormatException('Invalid timeout value: ${results['timeout']}'); - } - } - - if (verbose) { - print('[VERBOSE] Positional arguments: ${results.rest}'); - print('[VERBOSE] All arguments: ${results.arguments}'); - } - - if (results.rest.isEmpty) { - throw const FormatException('No URL provided.'); - } - - try { - method = Method.values.byName(results.rest.first.toLowerCase()); - urlPos = 1; - } on Error { - if (verbose) print('[VERBOSE] Using default method: GET'); - } - - if (verbose) print('[VERBOSE] Method: $method'); - - if (results.rest.length <= urlPos) { - throw const FormatException('No URL provided.'); - } - - String url = results.rest[urlPos]; - - if (url.startsWith(RegExp(r':\d+'))) { - url = 'http://localhost$url'; - } - - if (verbose) print('[VERBOSE] URL: $url'); - - Uri uri = Uri.parse(url); - - if (uri.scheme.isEmpty) { - uri = Uri.parse('http://$uri'); - } - - if (verbose) print('[VERBOSE] URI: $uri'); - - final Map headers = { - 'User-Agent': results['user-agent'], - 'Content-Type': results['content-type'], - }; - - Response response; - - switch (method) { - case Method.head: - if (verbose) print('[VERBOSE] HEAD: $uri'); - response = await head(uri, headers: headers).timeout(timeout); - case Method.get: - if (verbose) print('[VERBOSE] GET: $uri'); - response = await get(uri, headers: headers).timeout(timeout); - case Method.post: - if (verbose) print('[VERBOSE] POST: $uri'); - response = await post(uri, headers: headers).timeout(timeout); - case Method.put: - if (verbose) print('[VERBOSE] PUT: $uri'); - response = await put(uri, headers: headers).timeout(timeout); - case Method.patch: - if (verbose) print('[VERBOSE] PATCH: $uri'); - response = await patch(uri, headers: headers).timeout(timeout); - case Method.delete: - if (verbose) print('[VERBOSE] DELETE: $uri'); - response = await delete(uri, headers: headers).timeout(timeout); - } - - final int code = response.statusCode; - - if (verbose) print('[VERBOSE] Status code: $code'); - - int exitCode; - - if (code < 199) { - exitCode = 9; - } else if (code >= 200 && code < 300) { - exitCode = 0; - } else if (code >= 545) { - exitCode = 255; - } else { - exitCode = code - 290; - } - - return ReturnedData( - exitCode: exitCode, - statusCode: code, - canFail: results['fail'], - ); - } on FormatException catch (e) { - print(''); - print(e.message); - print(''); - printUsage(argParser); - return ReturnedData(exitCode: 1, statusCode: 0, canFail: true); - } on Exception catch (e) { - print(''); - print(e); - print(''); - return ReturnedData(exitCode: 7, statusCode: 0, canFail: true); - } on Error catch (e) { - print(''); - print(e); - print(''); - return ReturnedData(exitCode: 8, statusCode: 0, canFail: true); - } -} diff --git a/check.iml b/check.iml deleted file mode 100644 index 75734c9..0000000 --- a/check.iml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/helpers/create_index.dart b/helpers/create_index.dart index 361343a..0416c36 100644 --- a/helpers/create_index.dart +++ b/helpers/create_index.dart @@ -4,51 +4,119 @@ import 'package:markdown/markdown.dart'; void main(List args) { if (args.length != 2) { - print('Invalid parameters.'); + print('Uso: dart run helpers/create_index.dart '); exit(10); } final File mdFile = File(args.first); if (!mdFile.existsSync()) { - print('Markdown file not exists.'); + print('Erro: Arquivo Markdown não encontrado.'); exit(20); } const String template = ''' - + testainers - + + -
+
{{body}}
- '''; + final String generatedHtml = template.replaceAll( + '{{body}}', + markdownToHtml( + mdFile.readAsStringSync(), + extensionSet: ExtensionSet.gitHubWeb, + ), + ); + File(args.last) ..createSync(recursive: true) - ..writeAsStringSync( - template.replaceAll( - '{{body}}', - markdownToHtml( - mdFile.readAsStringSync(), - extensionSet: ExtensionSet.gitHubWeb, - ), - ), - ); -} + ..writeAsStringSync(generatedHtml); + + print('Página HTML gerada com sucesso em: ${args.last}'); +} \ No newline at end of file diff --git a/helpers/package.dart b/helpers/package.dart deleted file mode 100644 index 3bc6705..0000000 --- a/helpers/package.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -void main() { - final Map check = { - 'name': 'check', - 'rootUri': '../', - 'packageUri': 'lib/', - 'languageVersion': '3.0', - }; - - final Map package = { - 'configVersion': 2, - 'packages': >[check], - 'generated': DateTime.now().toIso8601String(), - 'generator': 'pub', - 'generatorVersion': '3.0.0', - }; - - File('coverage/package.json') - ..createSync(recursive: true) - ..writeAsStringSync(json.encode(package)); -} diff --git a/helpers/pix.png b/helpers/pix.png deleted file mode 100644 index 15dbaa0bbe1c0d7435010a0a66a7d51111d4876d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24597 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 IeV2s)0ghv$R{#J2 diff --git a/helpers/update_version.dart b/helpers/update_version.dart new file mode 100644 index 0000000..f4acbfe --- /dev/null +++ b/helpers/update_version.dart @@ -0,0 +1,24 @@ +import 'dart:io'; + +void main(List args) { + if (args.isEmpty) { + print('Error: No version provided.'); + exit(1); + } + + final String version = args.first; + final File file = File('lib/main.dart'); + + if (!file.existsSync()) { + print('Error: File not found: ${file.path}'); + exit(1); + } + + String content = file.readAsStringSync(); + + final RegExp regex = RegExp(r"const String version = 'dev';"); + content = content.replaceAll(regex, "const String version = '$version';"); + + file.writeAsStringSync(content); + print('Version updated to $version in lib/main.dart!'); +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..d0d46d5 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,187 @@ + +import 'package:args/args.dart'; +import 'package:check/returned_data.dart'; +import 'package:http/http.dart'; + +const String version = 'dev'; + +enum Method { head, get, post, put, patch, delete } + +ArgParser buildParser() { + return ArgParser() + ..addFlag( + 'help', + abbr: 'h', + negatable: false, + help: 'Print this usage information.', + ) + ..addOption( + 'timeout', + abbr: 't', + help: 'Set the timeout for the request.', + defaultsTo: '10', + valueHelp: 'in seconds', + ) + ..addOption( + 'content-type', + help: 'Set the content type for the request.', + defaultsTo: 'application/json', + valueHelp: 'type', + ) + ..addOption( + 'user-agent', + help: 'Set the user agent for the request.', + defaultsTo: 'check/$version', + valueHelp: 'agent', + ) + ..addFlag('fail', help: 'Fail on non-200 status code.', defaultsTo: true) + ..addFlag( + 'verbose', + abbr: 'v', + negatable: false, + help: 'Show additional command output.', + ) + ..addFlag('version', negatable: false, help: 'Print the tool version.'); +} + +void printUsage(ArgParser argParser) { + print('Usage: check [METHOD] URL'); + print(argParser.usage); +} + +Future check(List arguments) async { + final ArgParser argParser = buildParser(); + try { + final ArgResults results = argParser.parse(arguments); + final verbose = results.wasParsed('verbose'); + Method method = Method.get; + int urlPos = 0; + Duration timeout = const Duration(seconds: 10); + + if (results.wasParsed('help')) { + printUsage(argParser); + return ReturnedData.empty(); + } + + if (results.wasParsed('version')) { + print('check version: $version'); + return ReturnedData.empty(); + } + + if (results.wasParsed('timeout')) { + try { + final int value = int.parse(results['timeout']); + if (value < 1) { + throw FormatException('Invalid timeout value: ${results['timeout']}'); + } + timeout = Duration(seconds: value); + } on Exception { + throw FormatException('Invalid timeout value: ${results['timeout']}'); + } + } + + if (verbose) { + print('[VERBOSE] Positional arguments: ${results.rest}'); + print('[VERBOSE] All arguments: ${results.arguments}'); + } + + if (results.rest.isEmpty) { + throw const FormatException('No URL provided.'); + } + + try { + method = Method.values.byName(results.rest.first.toLowerCase()); + urlPos = 1; + } on Error { + if (verbose) print('[VERBOSE] Using default method: GET'); + } + + if (verbose) print('[VERBOSE] Method: $method'); + + if (results.rest.length <= urlPos) { + throw const FormatException('No URL provided.'); + } + + String url = results.rest[urlPos]; + + if (url.startsWith(RegExp(r':\d+'))) { + url = 'http://localhost$url'; + } + + if (verbose) print('[VERBOSE] URL: $url'); + + Uri uri = Uri.parse(url); + + if (uri.scheme.isEmpty) { + uri = Uri.parse('http://$uri'); + } + + if (verbose) print('[VERBOSE] URI: $uri'); + + final Map headers = { + 'User-Agent': results['user-agent'], + 'Content-Type': results['content-type'], + }; + + Response response; + + switch (method) { + case Method.head: + if (verbose) print('[VERBOSE] HEAD: $uri'); + response = await head(uri, headers: headers).timeout(timeout); + case Method.get: + if (verbose) print('[VERBOSE] GET: $uri'); + response = await get(uri, headers: headers).timeout(timeout); + case Method.post: + if (verbose) print('[VERBOSE] POST: $uri'); + response = await post(uri, headers: headers).timeout(timeout); + case Method.put: + if (verbose) print('[VERBOSE] PUT: $uri'); + response = await put(uri, headers: headers).timeout(timeout); + case Method.patch: + if (verbose) print('[VERBOSE] PATCH: $uri'); + response = await patch(uri, headers: headers).timeout(timeout); + case Method.delete: + if (verbose) print('[VERBOSE] DELETE: $uri'); + response = await delete(uri, headers: headers).timeout(timeout); + } + + final int code = response.statusCode; + + if (verbose) print('[VERBOSE] Status code: $code'); + + int exitCode; + + if (code < 199) { + exitCode = 9; + } else if (code >= 200 && code < 300) { + exitCode = 0; + } else if (code >= 545) { + exitCode = 255; + } else { + exitCode = code - 290; + } + + return ReturnedData( + exitCode: exitCode, + statusCode: code, + canFail: results['fail'], + ); + } on FormatException catch (e) { + print(''); + print(e.message); + print(''); + printUsage(argParser); + return ReturnedData(exitCode: 1, statusCode: 0, canFail: true); + } on Exception catch (e) { + print(''); + print(e); + print(''); + return ReturnedData(exitCode: 7, statusCode: 0, canFail: true); + } on Error catch (e) { + print(''); + print(e); + print(''); + return ReturnedData(exitCode: 8, statusCode: 0, canFail: true); + } +} diff --git a/bin/returned_data.dart b/lib/returned_data.dart similarity index 100% rename from bin/returned_data.dart rename to lib/returned_data.dart diff --git a/pubspec.yaml b/pubspec.yaml index c044ee1..b39fa97 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ repository: https://github.com/testainers/check homepage: https://check.testainers.com environment: - sdk: ^3.9.0 + sdk: ^3.11.0 dependencies: # https://pub.dev/packages/args @@ -16,13 +16,16 @@ dependencies: dev_dependencies: # https://pub.dev/packages/lints - lints: ^6.0.0 + lints: ^6.1.0 # https://pub.dev/packages/markdown markdown: ^7.3.0 + # https://pub.dev/packages/coverage + coverage: ^1.15.0 + # https://pub.dev/packages/test - test: ^1.29.0 + test: ^1.31.0 # https://pub.dev/packages/testainers - testainers: ^0.3.0 + testainers: ^0.4.0 diff --git a/test/check_local_test.dart b/test/check_local_test.dart index 56e93ee..7ea58fb 100644 --- a/test/check_local_test.dart +++ b/test/check_local_test.dart @@ -1,25 +1,16 @@ +import 'package:check/main.dart'; +import 'package:check/returned_data.dart'; import 'package:test/test.dart'; import 'package:testainers/testainers.dart'; -import '../bin/check.dart'; -import '../bin/returned_data.dart'; - -/// -/// -/// void main() { - /// - /// - /// group('basic tests', () { final TestainersHttpbucket container = TestainersHttpbucket(); - /// setUpAll(() async { await container.start(); }); - /// test('Root Get', () async { expect( await check([':${container.httpPort}']), @@ -27,7 +18,6 @@ void main() { ); }); - /// test('Method GET', () async { expect( await check([':${container.httpPort}/methods']), @@ -35,7 +25,6 @@ void main() { ); }); - /// test('Method HEAD', () async { expect( await check(['HEAD', ':${container.httpPort}/methods']), @@ -43,7 +32,6 @@ void main() { ); }); - /// test('Method POST', () async { expect( await check(['POST', ':${container.httpPort}/methods']), @@ -51,7 +39,6 @@ void main() { ); }); - /// test('Method PUT', () async { expect( await check(['PUT', ':${container.httpPort}/methods']), @@ -59,7 +46,6 @@ void main() { ); }); - /// test('Method PATCH', () async { expect( await check(['PATCH', ':${container.httpPort}/methods']), @@ -67,7 +53,6 @@ void main() { ); }); - /// test('Method DELETE', () async { expect( await check(['DELETE', ':${container.httpPort}/methods']), @@ -75,7 +60,6 @@ void main() { ); }); - /// test('Status GET 200', () async { expect( await check([':${container.httpPort}/status/200']), @@ -83,7 +67,6 @@ void main() { ); }); - /// test('Status GET 201', () async { expect( await check([':${container.httpPort}/status/201']), @@ -91,7 +74,6 @@ void main() { ); }); - /// test('Status GET 202', () async { expect( await check([':${container.httpPort}/status/202']), @@ -99,7 +81,6 @@ void main() { ); }); - /// test('Status GET 204', () async { expect( await check([':${container.httpPort}/status/204']), @@ -107,7 +88,6 @@ void main() { ); }); - /// test('Status GET 300', () async { expect( await check([':${container.httpPort}/status/300']), @@ -115,7 +95,6 @@ void main() { ); }); - /// test('Status GET 300 no Fail', () async { expect( await check(['--no-fail', ':${container.httpPort}/status/300']), @@ -123,7 +102,6 @@ void main() { ); }); - /// test('Status GET 300', () async { expect( await check(['--no-fail', ':${container.httpPort}/status/300']), @@ -131,7 +109,6 @@ void main() { ); }); - /// test('Status GET 389', () async { expect( await check([':${container.httpPort}/status/389']), @@ -139,7 +116,6 @@ void main() { ); }); - /// test('Status GET 390', () async { expect( await check([':${container.httpPort}/status/390']), @@ -147,7 +123,6 @@ void main() { ); }); - /// test('Status GET 399', () async { expect( await check([':${container.httpPort}/status/399']), @@ -155,7 +130,6 @@ void main() { ); }); - /// test('Status GET 400', () async { expect( await check([':${container.httpPort}/status/400']), @@ -163,7 +137,6 @@ void main() { ); }); - /// test('Status GET 400 no Fail', () async { expect( await check(['--no-fail', ':${container.httpPort}/status/400']), @@ -171,7 +144,6 @@ void main() { ); }); - /// test('Status GET 401', () async { expect( await check([':${container.httpPort}/status/401']), @@ -179,7 +151,6 @@ void main() { ); }); - /// test('Status GET 403', () async { expect( await check([':${container.httpPort}/status/403']), @@ -187,7 +158,6 @@ void main() { ); }); - /// test('Status GET 404', () async { expect( await check([':${container.httpPort}/status/404']), @@ -195,7 +165,6 @@ void main() { ); }); - /// test('Status GET 500', () async { expect( await check([':${container.httpPort}/status/500']), @@ -203,7 +172,6 @@ void main() { ); }); - /// test('Status GET 500 no Fail', () async { expect( await check(['--no-fail', ':${container.httpPort}/status/500']), @@ -211,7 +179,6 @@ void main() { ); }); - /// test('Status GET 544', () async { expect( await check([':${container.httpPort}/status/544']), @@ -219,7 +186,6 @@ void main() { ); }); - /// test('Status GET 545', () async { expect( await check([':${container.httpPort}/status/545']), @@ -227,7 +193,6 @@ void main() { ); }); - /// test('Status GET 546', () async { expect( await check([':${container.httpPort}/status/546']), @@ -235,7 +200,6 @@ void main() { ); }); - /// test('Status GET 599', () async { expect( await check([':${container.httpPort}/status/599']), @@ -243,7 +207,6 @@ void main() { ); }); - /// test('Status GET 599 no Fail', () async { expect( await check(['--no-fail', ':${container.httpPort}/status/599']), @@ -251,7 +214,6 @@ void main() { ); }); - /// tearDownAll(container.stop); }); } From 6b1719c8cf5485029f8d25f939939be4375be5e3 Mon Sep 17 00:00:00 2001 From: Eduardo Folly Date: Sun, 24 May 2026 15:22:55 -0300 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=94=96=20Version=20bump=200.0.4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 664490a..c71f635 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,9 @@ -## 0.0.5 - 2026-05-24 +## 0.0.4 - 2026-05-24 - Multiple architecture build. - Fix code coverage. - Updating project plugins, CI and dependencies. -## 0.0.4 - 2026-01-11 - -- Updating project plugins, CI and dependencies. - ## 0.0.3 [2024-06-01] - Updating project plugins, CI and dependencies.