diff --git a/.github/workflows/run_integration_tests.yaml b/.github/workflows/run_integration_tests.yaml new file mode 100644 index 00000000..7be30d2a --- /dev/null +++ b/.github/workflows/run_integration_tests.yaml @@ -0,0 +1,41 @@ +name: Run integration tests +on: + push: + branches: [ main ] + paths: + - server/** + - client/** + - tests/** + - ".github/workflows/run_integration_tests.yaml" + pull_request: + branches: [ main ] + paths: + - server/** + - client/** + - tests/** + - ".github/workflows/run_integration_tests.yaml" +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + runs-on: [self-hosted, linux, large, jammy, x64] + defaults: + run: + working-directory: tests + steps: + - uses: actions/checkout@v4 + - name: Set up docker buildx + uses: docker/setup-buildx-action@v3 + with: + config-inline: | + [registry."docker.io"] + mirrors = ["https://github-runner-dockerhub-cache.canonical.com:5000"] + - name: Install docker compose + run: | + sudo apt update + sudo apt install -y docker-compose-v2 + - name: Build and run integration tests + run: | + docker compose up --build --abort-on-container-exit diff --git a/.github/workflows/test_client.yaml b/.github/workflows/test_client.yaml index 9a50294c..ddd521ca 100644 --- a/.github/workflows/test_client.yaml +++ b/.github/workflows/test_client.yaml @@ -5,11 +5,13 @@ on: paths: - client/** - Cargo.* + - ".github/workflows/test_client.yaml" pull_request: branches: [ main ] paths: - client/** - Cargo.* + - ".github/workflows/test_client.yaml" concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true diff --git a/.github/workflows/test_server.yaml b/.github/workflows/test_server.yaml index 78569df3..233d80d7 100644 --- a/.github/workflows/test_server.yaml +++ b/.github/workflows/test_server.yaml @@ -4,10 +4,12 @@ on: branches: [ main ] paths: - server/** + - ".github/workflows/test_server.yaml" pull_request: branches: [ main ] paths: - server/** + - ".github/workflows/test_server.yaml" concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true diff --git a/Cargo.lock b/Cargo.lock index 44daff9e..97f1a3c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -284,9 +284,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "h2" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" dependencies = [ "atomic-waker", "bytes", @@ -390,9 +390,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" dependencies = [ "bytes", "futures-channel", @@ -642,9 +642,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "7a73e9fe3c49d7afb2ace819fa181a287ce54a0983eda4e0eb05c22f82ffe534" [[package]] name = "js-sys" @@ -663,9 +663,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.162" +version = "0.2.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" [[package]] name = "linux-raw-sys" @@ -1040,9 +1040,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.38.40" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags", "errno", @@ -1053,9 +1053,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.16" +version = "0.23.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" +checksum = "7f1a745511c54ba6d4465e8d5dfbd81b45791756de28d4981af70d6dca128f1e" dependencies = [ "once_cell", "rustls-pki-types", @@ -1098,9 +1098,9 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "schannel" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] @@ -1454,9 +1454,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-width" diff --git a/client/hwlib/debian/control b/client/hwlib/debian/control index 51351c28..8f8c308b 100644 --- a/client/hwlib/debian/control +++ b/client/hwlib/debian/control @@ -10,7 +10,7 @@ Build-Depends: cargo:native, Maintainer: Canonical Hardware Certification Uploaders: Nadzeya Hutsko Standards-Version: 4.6.2 -XS-Vendored-Sources-Rust: addr2line@0.24.2, adler2@2.0.0, aho-corasick@1.1.3, anyhow@1.0.93, atomic-waker@1.1.2, autocfg@1.4.0, backtrace@0.3.74, base64@0.22.1, bitflags@2.6.0, bumpalo@3.16.0, bytes@1.8.0, cc@1.2.1, cfg-if@1.0.0, core-foundation-sys@0.8.7, core-foundation@0.10.0, core-foundation@0.9.4, deranged@0.3.11, diff@0.1.13, displaydoc@0.2.5, either@1.13.0, encoding_rs@0.8.35, equivalent@1.0.1, errno@0.3.9, fastrand@2.2.0, fnv@1.0.7, foreign-types-shared@0.1.1, foreign-types@0.3.2, form_urlencoded@1.2.1, futures-channel@0.3.31, futures-core@0.3.31, futures-sink@0.3.31, futures-task@0.3.31, futures-util@0.3.31, getopts@0.2.21, getrandom@0.2.15, gimli@0.31.1, h2@0.4.6, hashbrown@0.15.1, heck@0.5.0, hermit-abi@0.3.9, http-body-util@0.1.2, http-body@1.0.1, http@1.1.0, httparse@1.9.5, hyper-rustls@0.27.3, hyper-tls@0.6.0, hyper-util@0.1.10, hyper@1.5.0, icu_collections@1.5.0, icu_locid@1.5.0, icu_locid_transform@1.5.0, icu_locid_transform_data@1.5.0, icu_normalizer@1.5.0, icu_normalizer_data@1.5.0, icu_properties@1.5.1, icu_properties_data@1.5.0, icu_provider@1.5.0, icu_provider_macros@1.5.0, idna@1.0.3, idna_adapter@1.2.0, indexmap@2.6.0, indoc@2.0.5, io-kit-sys@0.4.1, ipnet@2.10.1, itertools@0.13.0, itoa@1.0.11, js-sys@0.3.72, lazy_static@1.5.0, libc@0.2.162, linux-raw-sys@0.4.14, litemap@0.7.3, log@0.4.22, mach2@0.4.2, memchr@2.7.4, memoffset@0.9.1, mime@0.3.17, miniz_oxide@0.8.0, mio@1.0.2, native-tls@0.2.12, num-conv@0.1.0, object@0.36.5, once_cell@1.20.2, openssl-macros@0.1.1, openssl-probe@0.1.5, openssl-sys@0.9.104, openssl@0.10.68, percent-encoding@2.3.1, pin-project-lite@0.2.15, pin-utils@0.1.0, pkg-config@0.3.31, portable-atomic@1.9.0, powerfmt@0.2.0, pretty_assertions@1.4.1, proc-macro2@1.0.89, pyo3-build-config@0.23.1, pyo3-ffi@0.23.1, pyo3-macros-backend@0.23.1, pyo3-macros@0.23.1, pyo3@0.23.1, quote@1.0.37, regex-automata@0.4.9, regex-syntax@0.8.5, regex@1.11.1, reqwest@0.12.9, ring@0.17.8, rustc-demangle@0.1.24, rustix@0.38.40, rustls-pemfile@2.2.0, rustls-pki-types@1.10.0, rustls-webpki@0.102.8, rustls@0.23.16, ryu@1.0.18, schannel@0.1.26, security-framework-sys@2.12.1, security-framework@2.11.1, serde@1.0.215, serde_derive@1.0.215, serde_json@1.0.133, serde_urlencoded@0.7.1, shlex@1.3.0, simple_test_case@1.2.0, slab@0.4.9, smallvec@1.13.2, smbios-lib@0.9.2, socket2@0.5.7, spin@0.9.8, stable_deref_trait@1.2.0, subtle@2.6.1, syn@2.0.87, sync_wrapper@1.0.1, synstructure@0.13.1, system-configuration-sys@0.6.0, system-configuration@0.6.1, target-lexicon@0.12.16, tempfile@3.14.0, time-core@0.1.2, time-macros@0.2.18, time@0.3.36, tinystr@0.7.6, tokio-macros@2.4.0, tokio-native-tls@0.3.1, tokio-rustls@0.26.0, tokio-util@0.7.12, tokio@1.41.1, tower-service@0.3.3, tracing-core@0.1.32, tracing@0.1.40, try-lock@0.2.5, unicode-ident@1.0.13, unicode-width@0.1.14, unindent@0.2.3, untrusted@0.9.0, url@2.5.3, utf16_iter@1.0.5, utf8_iter@1.0.4, vcpkg@0.2.15, want@0.3.1, wasi@0.11.0+wasi-snapshot-preview1, wasm-bindgen-backend@0.2.95, wasm-bindgen-futures@0.4.45, wasm-bindgen-macro-support@0.2.95, wasm-bindgen-macro@0.2.95, wasm-bindgen-shared@0.2.95, wasm-bindgen@0.2.95, web-sys@0.3.72, windows-registry@0.2.0, windows-result@0.2.0, windows-strings@0.1.0, windows-sys@0.52.0, windows-sys@0.59.0, windows-targets@0.52.6, windows_aarch64_gnullvm@0.52.6, windows_aarch64_msvc@0.52.6, windows_i686_gnu@0.52.6, windows_i686_gnullvm@0.52.6, windows_i686_msvc@0.52.6, windows_x86_64_gnu@0.52.6, windows_x86_64_gnullvm@0.52.6, windows_x86_64_msvc@0.52.6, write16@1.0.0, writeable@0.5.5, yansi@1.0.1, yoke-derive@0.7.4, yoke@0.7.4, zerofrom-derive@0.1.4, zerofrom@0.1.4, zeroize@1.8.1, zerovec-derive@0.10.3, zerovec@0.10.4 +XS-Vendored-Sources-Rust: addr2line@0.24.2, adler2@2.0.0, aho-corasick@1.1.3, anyhow@1.0.93, atomic-waker@1.1.2, autocfg@1.4.0, backtrace@0.3.74, base64@0.22.1, bitflags@2.6.0, bumpalo@3.16.0, bytes@1.8.0, cc@1.2.1, cfg-if@1.0.0, core-foundation-sys@0.8.7, core-foundation@0.10.0, core-foundation@0.9.4, deranged@0.3.11, diff@0.1.13, displaydoc@0.2.5, either@1.13.0, encoding_rs@0.8.35, equivalent@1.0.1, errno@0.3.9, fastrand@2.2.0, fnv@1.0.7, foreign-types-shared@0.1.1, foreign-types@0.3.2, form_urlencoded@1.2.1, futures-channel@0.3.31, futures-core@0.3.31, futures-sink@0.3.31, futures-task@0.3.31, futures-util@0.3.31, getopts@0.2.21, getrandom@0.2.15, gimli@0.31.1, h2@0.4.7, hashbrown@0.15.1, heck@0.5.0, hermit-abi@0.3.9, http-body-util@0.1.2, http-body@1.0.1, http@1.1.0, httparse@1.9.5, hyper-rustls@0.27.3, hyper-tls@0.6.0, hyper-util@0.1.10, hyper@1.5.1, icu_collections@1.5.0, icu_locid@1.5.0, icu_locid_transform@1.5.0, icu_locid_transform_data@1.5.0, icu_normalizer@1.5.0, icu_normalizer_data@1.5.0, icu_properties@1.5.1, icu_properties_data@1.5.0, icu_provider@1.5.0, icu_provider_macros@1.5.0, idna@1.0.3, idna_adapter@1.2.0, indexmap@2.6.0, indoc@2.0.5, io-kit-sys@0.4.1, ipnet@2.10.1, itertools@0.13.0, itoa@1.0.12, js-sys@0.3.72, lazy_static@1.5.0, libc@0.2.164, linux-raw-sys@0.4.14, litemap@0.7.3, log@0.4.22, mach2@0.4.2, memchr@2.7.4, memoffset@0.9.1, mime@0.3.17, miniz_oxide@0.8.0, mio@1.0.2, native-tls@0.2.12, num-conv@0.1.0, object@0.36.5, once_cell@1.20.2, openssl-macros@0.1.1, openssl-probe@0.1.5, openssl-sys@0.9.104, openssl@0.10.68, percent-encoding@2.3.1, pin-project-lite@0.2.15, pin-utils@0.1.0, pkg-config@0.3.31, portable-atomic@1.9.0, powerfmt@0.2.0, pretty_assertions@1.4.1, proc-macro2@1.0.89, pyo3-build-config@0.23.1, pyo3-ffi@0.23.1, pyo3-macros-backend@0.23.1, pyo3-macros@0.23.1, pyo3@0.23.1, quote@1.0.37, regex-automata@0.4.9, regex-syntax@0.8.5, regex@1.11.1, reqwest@0.12.9, ring@0.17.8, rustc-demangle@0.1.24, rustix@0.38.41, rustls-pemfile@2.2.0, rustls-pki-types@1.10.0, rustls-webpki@0.102.8, rustls@0.23.17, ryu@1.0.18, schannel@0.1.27, security-framework-sys@2.12.1, security-framework@2.11.1, serde@1.0.215, serde_derive@1.0.215, serde_json@1.0.133, serde_urlencoded@0.7.1, shlex@1.3.0, simple_test_case@1.2.0, slab@0.4.9, smallvec@1.13.2, smbios-lib@0.9.2, socket2@0.5.7, spin@0.9.8, stable_deref_trait@1.2.0, subtle@2.6.1, syn@2.0.87, sync_wrapper@1.0.1, synstructure@0.13.1, system-configuration-sys@0.6.0, system-configuration@0.6.1, target-lexicon@0.12.16, tempfile@3.14.0, time-core@0.1.2, time-macros@0.2.18, time@0.3.36, tinystr@0.7.6, tokio-macros@2.4.0, tokio-native-tls@0.3.1, tokio-rustls@0.26.0, tokio-util@0.7.12, tokio@1.41.1, tower-service@0.3.3, tracing-core@0.1.32, tracing@0.1.40, try-lock@0.2.5, unicode-ident@1.0.14, unicode-width@0.1.14, unindent@0.2.3, untrusted@0.9.0, url@2.5.3, utf16_iter@1.0.5, utf8_iter@1.0.4, vcpkg@0.2.15, want@0.3.1, wasi@0.11.0+wasi-snapshot-preview1, wasm-bindgen-backend@0.2.95, wasm-bindgen-futures@0.4.45, wasm-bindgen-macro-support@0.2.95, wasm-bindgen-macro@0.2.95, wasm-bindgen-shared@0.2.95, wasm-bindgen@0.2.95, web-sys@0.3.72, windows-registry@0.2.0, windows-result@0.2.0, windows-strings@0.1.0, windows-sys@0.52.0, windows-sys@0.59.0, windows-targets@0.52.6, windows_aarch64_gnullvm@0.52.6, windows_aarch64_msvc@0.52.6, windows_i686_gnu@0.52.6, windows_i686_gnullvm@0.52.6, windows_i686_msvc@0.52.6, windows_x86_64_gnu@0.52.6, windows_x86_64_gnullvm@0.52.6, windows_x86_64_msvc@0.52.6, write16@1.0.0, writeable@0.5.5, yansi@1.0.1, yoke-derive@0.7.4, yoke@0.7.4, zerofrom-derive@0.1.4, zerofrom@0.1.4, zeroize@1.8.1, zerovec-derive@0.10.3, zerovec@0.10.4 Vcs-Git: https://github.com/canonical/hardware-api.git [client/hwlib/src] Vcs-Browser: https://github.com/canonical/hardware-api/tree/main/client/hwlib/src Homepage: https://github.com/canonical/hardware-api diff --git a/client/hwlib/src/py_bindings.rs b/client/hwlib/src/py_bindings.rs index 86546e68..be140f14 100644 --- a/client/hwlib/src/py_bindings.rs +++ b/client/hwlib/src/py_bindings.rs @@ -49,8 +49,8 @@ fn send_certification_request(py: Python, url: String) -> PyResult { match response { Ok(response_value) => { let json_str = serde_json::json!(response_value).to_string(); - let json: PyObject = PyString::new_bound(py, &json_str).into(); - let json_module = py.import_bound("json")?; + let json: PyObject = PyString::new(py, &json_str).into(); + let json_module = py.import("json")?; let json_object: PyObject = json_module.call_method1("loads", (json,))?.into(); Ok(json_object) diff --git a/tests/Cargo.toml b/tests/Cargo.toml new file mode 100644 index 00000000..73e9fdca --- /dev/null +++ b/tests/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "integration-tests" +version = "0.1.0" +edition = "2021" + +[dependencies] +hwlib = { path = "client/hwlib" } +tokio = { version = "1.0", features = ["full", "test-util"] } +anyhow = "1.0" +simple_test_case = "1.2.0" + +[[test]] +name = "integration" +path = "src/integration_test.rs" \ No newline at end of file diff --git a/tests/Dockerfile b/tests/Dockerfile new file mode 100644 index 00000000..3adfc982 --- /dev/null +++ b/tests/Dockerfile @@ -0,0 +1,22 @@ +FROM ubuntu:24.04 + +# Install Rust and required packages +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \ + curl \ + build-essential \ + pkgconf \ + libssl-dev \ + lsb-release \ + kmod \ + python3 \ + python3-pip \ + rustup + +RUN rustup default stable + +WORKDIR /app + +COPY Cargo.toml . +COPY src src/ + +CMD ["cargo", "test", "--test", "integration", "--", "--nocapture"] diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000..cb9df264 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,14 @@ +# Integration tests for the project + +This folder contains the tests for the hardware-api project that check +how client and server communicate. + +To run them, first install +[docker](https://docs.docker.com/engine/install/ubuntu/) and [setup +permissions](https://docs.docker.com/engine/install/linux-postinstall/). + +Then, run the tests with `docker compose`: + +```sh +docker compose up --build --abort-on-container-exit +``` diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml new file mode 100644 index 00000000..e0de422e --- /dev/null +++ b/tests/docker-compose.yml @@ -0,0 +1,22 @@ +services: + hwapi-server-integration: + build: + context: ../server + dockerfile: Dockerfile + args: + IMPORT_TOOL_PATH: "" + C3_URL: https://c3_url + DB_URL: sqlite:////home/server/test-integration.db + ports: + - "8080:8080" + + integration-tests: + build: + context: . + dockerfile: Dockerfile + depends_on: + - hwapi-server-integration + volumes: + - ../client:/app/client + environment: + - API_URL=http://hwapi-server-integration:8080 diff --git a/tests/src/integration_test.rs b/tests/src/integration_test.rs new file mode 100644 index 00000000..505d11bd --- /dev/null +++ b/tests/src/integration_test.rs @@ -0,0 +1,88 @@ +/* Copyright 2024 Canonical Ltd. + * All rights reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Written by: + * Nadzeya Hutsko + */ + +use anyhow::Result; +use std::path::PathBuf; +use simple_test_case::test_case; +use hwlib::{ + models::request_validators::{CertificationStatusRequest, Paths}, + models::response_validators::CertificationStatusResponse, + send_certification_status_request, +}; + +fn get_test_device_paths(device_type: &str) -> Paths { + let base_path = PathBuf::from("/app/client/hwlib/test_data/amd64").join(device_type); + Paths { + smbios_entry_filepath: base_path.join("smbios_entry_point"), + smbios_table_filepath: base_path.join("DMI"), + cpuinfo_filepath: PathBuf::from("./none"), + max_cpu_frequency_filepath: base_path.join("cpuinfo_max_freq"), + device_tree_dirpath: PathBuf::from("./none"), + proc_version_filepath: base_path.join("version"), + } +} + +#[test_case( + "dgx_station"; + "dgx_station" // test name +)] +#[test_case( + "dell_xps13"; + "dell_xps13" +)] +#[test_case( + "thinkstation_p620"; + "thinkstation_p620" +)] +#[tokio::test] +async fn test_certification_request(dir_path: &str) -> Result<()> { + let api_url = match std::env::var("API_URL") { + Ok(url) => url, + Err(..) => panic!("API_URL environment variable must be specified"), + }; + let cert_request = CertificationStatusRequest::new(get_test_device_paths(dir_path))?; + let response = send_certification_status_request(api_url, &cert_request).await?; + + // Currently all the responses should match the "Not Seen" status, it'll be updated + // once snapshot tests are defined + match response { + CertificationStatusResponse::NotSeen => { + // Here, we don't need any further assertions. + // We simply want to ensure this case is hit. + }, + _ => { + panic!("Expected response to be NotSeen, but it was {:?}", response); + }, + } + + Ok(()) +} + + +#[tokio::test] +async fn test_server_connection_error() -> Result<()> { + let result = send_certification_status_request( + "http://non-existent-server:8080".to_string(), + &CertificationStatusRequest::new(get_test_device_paths("dell_xps13"))?, + ).await; + + assert!(result.is_err()); + Ok(()) +}