ci: add nightly external contract tests
This commit is contained in:
30
.github/workflows/external-contracts.yml
vendored
Normal file
30
.github/workflows/external-contracts.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
name: External contracts
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 3 * * *"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_PROFILE_TEST_DEBUG: line-tables-only
|
||||||
|
CARGO_INCREMENTAL: 0
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
external-contracts:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||||
|
|
||||||
|
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
|
||||||
|
|
||||||
|
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||||||
|
|
||||||
|
# These tests intentionally call real third-party APIs. Unit/integration
|
||||||
|
# tests mock providers, but a nightly contract check catches external API
|
||||||
|
# drift that would otherwise only appear at runtime.
|
||||||
|
- name: Run external contract tests
|
||||||
|
run: cargo test --locked --test external_contract -- --ignored --test-threads=1
|
||||||
@@ -102,6 +102,14 @@ cargo test --locked
|
|||||||
|
|
||||||
The crate denies unsafe code and enables strict Clippy groups: `all`, `pedantic`, `nursery` and `cargo`, with only dependency-version noise allowed.
|
The crate denies unsafe code and enables strict Clippy groups: `all`, `pedantic`, `nursery` and `cargo`, with only dependency-version noise allowed.
|
||||||
|
|
||||||
|
## External contract checks
|
||||||
|
|
||||||
|
Normal tests mock third-party APIs. A scheduled GitHub Actions workflow runs ignored contract tests nightly against the real PokéAPI and FunTranslations APIs to catch provider contract drift early:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo test --locked --test external_contract -- --ignored --test-threads=1
|
||||||
|
```
|
||||||
|
|
||||||
## Docker multi-arch build
|
## Docker multi-arch build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
56
tests/external_contract.rs
Normal file
56
tests/external_contract.rs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
use pokedex_api::{
|
||||||
|
clients::{funtranslations::TranslationClient, pokeapi::PokeApiClient},
|
||||||
|
domain::TranslationKind,
|
||||||
|
};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
const POKEAPI_BASE_URL: &str = "https://pokeapi.co/api/v2";
|
||||||
|
const FUN_TRANSLATIONS_BASE_URL: &str = "https://api.funtranslations.mercxry.me/v1";
|
||||||
|
const CONTRACT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[ignore = "nightly external contract test: calls the real PokéAPI"]
|
||||||
|
async fn pokeapi_species_contract_still_matches_what_we_parse() {
|
||||||
|
let client =
|
||||||
|
PokeApiClient::new(POKEAPI_BASE_URL, CONTRACT_TIMEOUT).expect("client should build");
|
||||||
|
|
||||||
|
let pokemon = client
|
||||||
|
.get_pokemon_info("mewtwo")
|
||||||
|
.await
|
||||||
|
.expect("PokéAPI species contract should still parse");
|
||||||
|
|
||||||
|
assert_eq!(pokemon.name, "mewtwo");
|
||||||
|
assert_eq!(pokemon.habitat, "rare");
|
||||||
|
assert!(pokemon.is_legendary);
|
||||||
|
assert!(!pokemon.description.trim().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[ignore = "nightly external contract test: calls the real FunTranslations API"]
|
||||||
|
async fn funtranslations_yoda_contract_still_matches_what_we_parse() {
|
||||||
|
let client = TranslationClient::new(FUN_TRANSLATIONS_BASE_URL, CONTRACT_TIMEOUT)
|
||||||
|
.expect("client should build");
|
||||||
|
|
||||||
|
let translated = client
|
||||||
|
.translate(TranslationKind::Yoda, "It was created by science.")
|
||||||
|
.await
|
||||||
|
.expect("FunTranslations Yoda contract should still parse");
|
||||||
|
|
||||||
|
assert!(!translated.trim().is_empty());
|
||||||
|
assert_ne!(translated, "It was created by science.");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[ignore = "nightly external contract test: calls the real FunTranslations API"]
|
||||||
|
async fn funtranslations_shakespeare_contract_still_matches_what_we_parse() {
|
||||||
|
let client = TranslationClient::new(FUN_TRANSLATIONS_BASE_URL, CONTRACT_TIMEOUT)
|
||||||
|
.expect("client should build");
|
||||||
|
|
||||||
|
let translated = client
|
||||||
|
.translate(TranslationKind::Shakespeare, "You have a great friend.")
|
||||||
|
.await
|
||||||
|
.expect("FunTranslations Shakespeare contract should still parse");
|
||||||
|
|
||||||
|
assert!(!translated.trim().is_empty());
|
||||||
|
assert_ne!(translated, "You have a great friend.");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user