diff --git a/.github/workflows/regenerate.yml b/.github/workflows/regenerate.yml index 855e2eb..93e7864 100644 --- a/.github/workflows/regenerate.yml +++ b/.github/workflows/regenerate.yml @@ -2,31 +2,54 @@ name: Regenerate Client on: workflow_dispatch: + inputs: + openapi_spec_ref: + description: "Git ref in www.hotdata.dev for api/openapi.yaml (same file as sdk-python regenerate)." + required: false + default: main jobs: regenerate: runs-on: ubuntu-latest + env: + # Same OpenAPI document as https://github.com/hotdata-dev/sdk-python/blob/main/.github/workflows/regenerate.yml + OPENAPI_SPEC_OWNER: hotdata-dev + OPENAPI_SPEC_REPO: www.hotdata.dev + OPENAPI_SPEC_PATH: api/openapi.yaml + OPENAPI_SPEC_REF: ${{ inputs.openapi_spec_ref || 'main' }} steps: - name: Generate GitHub App token id: app-token - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 with: app-id: 3060111 private-key: ${{ secrets.HOTDATA_AUTOMATION_PRIVATE_KEY }} owner: hotdata-dev - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: token: ${{ steps.app-token.outputs.token }} - - name: Fetch merged OpenAPI spec + - uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0 + with: + distribution: temurin + java-version: "17" + + - name: Crate version for generator + id: crate + run: | + v=$(awk -F '"' '/^version = / { print $2; exit }' Cargo.toml) + echo "version=$v" >> "$GITHUB_OUTPUT" + + - name: Fetch OpenAPI spec (same source as sdk-python) env: GH_TOKEN: ${{ steps.app-token.outputs.token }} run: | + spec_url="https://api.github.com/repos/${OPENAPI_SPEC_OWNER}/${OPENAPI_SPEC_REPO}/contents/${OPENAPI_SPEC_PATH}?ref=${OPENAPI_SPEC_REF}" curl -sS -f -L \ -H "Accept: application/vnd.github.v3.raw" \ -H "Authorization: Bearer $GH_TOKEN" \ - https://api.github.com/repos/hotdata-dev/www.hotdata.dev/contents/api/openapi.yaml \ + "$spec_url" \ -o openapi.yaml - name: Clean existing source @@ -34,24 +57,28 @@ jobs: - name: Generate client env: - PACKAGE_VERSION: "0.1.0" + PACKAGE_VERSION: ${{ steps.crate.outputs.version }} run: | - npx @openapitools/openapi-generator-cli generate \ + npx --yes @openapitools/openapi-generator-cli generate \ -i openapi.yaml \ -g rust \ -o . \ - --additional-properties=packageName=hotdata,packageVersion=$PACKAGE_VERSION,library=reqwest,supportAsync=true \ + --additional-properties=packageName=hotdata,packageVersion=${PACKAGE_VERSION},library=reqwest,supportAsync=true \ --http-user-agent "hotdata-rust/${PACKAGE_VERSION}" \ --skip-validate-spec - - name: Clean up fetched spec - run: rm -f openapi.yaml + - name: Clean up generated artifacts + run: | + rm -f openapi.yaml + rm -f git_push.sh - - name: Verify generated client compiles - run: cargo check + - name: Verify generated client (tests) + run: | + cargo test + cargo test --no-default-features --features rustls - name: Create PR - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8 with: token: ${{ steps.app-token.outputs.token }} title: "chore: regenerate client from updated OpenAPI spec" diff --git a/Cargo.toml b/Cargo.toml index 6a0804c..f6edbcc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,7 @@ reqwest = { version = "^0.13", default-features = false, features = ["json", "mu default = ["native-tls"] native-tls = ["reqwest/native-tls"] rustls = ["reqwest/rustls"] + +[dev-dependencies] +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } +wiremock = "0.6" diff --git a/tests/smoke.rs b/tests/smoke.rs new file mode 100644 index 0000000..0f787f1 --- /dev/null +++ b/tests/smoke.rs @@ -0,0 +1,46 @@ +use hotdata::apis::configuration::{ApiKey, Configuration}; +use hotdata::apis::workspaces_api; +use hotdata::models; +use wiremock::matchers::{header, method, path}; +use wiremock::{Mock, MockServer, ResponseTemplate}; + +#[test] +fn error_model_deserializes() { + let e: models::Error = serde_json::from_str(r#"{"error":"not_found"}"#).unwrap(); + assert_eq!(e.error, "not_found"); +} + +#[test] +fn configuration_matches_python_sdk_usage() { + let mut c = Configuration::new(); + c.bearer_access_token = Some("YOUR_ACCESS_TOKEN".into()); + c.api_key = Some(ApiKey { + prefix: None, + key: "YOUR_WORKSPACE_ID".into(), + }); + assert!(c.bearer_access_token.is_some()); +} + +#[tokio::test] +async fn list_workspaces_hits_api_and_deserializes() { + let server = MockServer::start().await; + Mock::given(method("GET")) + .and(path("/v1/workspaces")) + .and(header("authorization", "Bearer test-token")) + .respond_with(ResponseTemplate::new(200).set_body_raw( + r#"{"ok":true,"workspaces":[{"public_id":"ws_1","name":"Demo","active":true,"favorite":false,"provision_status":"ready","namespace":"demo"}]}"#, + "application/json", + )) + .mount(&server) + .await; + + let mut config = Configuration::new(); + config.base_path = server.uri(); + config.bearer_access_token = Some("test-token".into()); + + let body = workspaces_api::list_workspaces(&config, None).await.unwrap(); + assert!(body.ok); + assert_eq!(body.workspaces.len(), 1); + assert_eq!(body.workspaces[0].public_id, "ws_1"); + assert_eq!(body.workspaces[0].name, "Demo"); +}