From 01e9d4818b26344db110fc39250ce267aea6430d Mon Sep 17 00:00:00 2001 From: Gabriel Simmer Date: Wed, 19 Jul 2023 08:48:24 +0100 Subject: [PATCH] Add basic tests --- .../{build-docker.yml => test-build.yml} | 20 ++++ flake.lock | 107 +++++++++++++----- flake.nix | 51 ++++++++- src/css.rs | 12 +- src/lib.rs | 101 ++++++++--------- src/main.rs | 87 +++++++------- 6 files changed, 246 insertions(+), 132 deletions(-) rename .gitea/workflows/{build-docker.yml => test-build.yml} (61%) diff --git a/.gitea/workflows/build-docker.yml b/.gitea/workflows/test-build.yml similarity index 61% rename from .gitea/workflows/build-docker.yml rename to .gitea/workflows/test-build.yml index df1af73..d3e5158 100644 --- a/.gitea/workflows/build-docker.yml +++ b/.gitea/workflows/test-build.yml @@ -5,7 +5,27 @@ on: - trunk jobs: + nix-flake-check: + runs-on: debian-latest + steps: + - name: Install prerequisites + run: apt update && apt install -y sudo + - name: Install Nix + uses: https://github.com/cachix/install-nix-action@v22 + with: + extra_nix_config: "experimental-features = nix-command flakes" + nix_path: nixpkgs=channel:nixos-23.05 + - name: Remove access_tokens + run: sed -i '/^access-tokens/d' /etc/nix/nix.conf + - name: Check out repository + uses: https://github.com/RouxAntoine/checkout@v3.5.4 + with: + ref: trunk + - name: Check codebase + run: nix flake check -L + arm-docker-build: + needs: nix-flake-check runs-on: debian-latest-arm steps: - name: Install prerequisites diff --git a/flake.lock b/flake.lock index fb3924e..49c30cd 100644 --- a/flake.lock +++ b/flake.lock @@ -6,11 +6,11 @@ "utils": "utils" }, "locked": { - "lastModified": 1667763126, - "narHash": "sha256-wKn3q3hICvEfs0m3CaeJmLCSyWVuVw/5LgDdh3QSXkU=", + "lastModified": 1682266158, + "narHash": "sha256-VK66KyF1doJ24yktb9rp7Yv7auS6i0P8EnJLhFOz+jY=", "owner": "Xe", "repo": "Xess", - "rev": "882799a25ce4fa4df014ad701aaa5a9b23ff9e85", + "rev": "3a85d1de06cd3420b4d56a8edd72cd57e6f0806e", "type": "github" }, "original": { @@ -19,6 +19,22 @@ "type": "github" } }, + "advisory-db": { + "flake": false, + "locked": { + "lastModified": 1689698236, + "narHash": "sha256-Qz9JxGKeA3jwuj1CdK9ejMJ7VsJRdiZniF8lx4mft9s=", + "owner": "rustsec", + "repo": "advisory-db", + "rev": "4aa517564d1d06f0e79784c8ad973a59d68aa9c8", + "type": "github" + }, + "original": { + "owner": "rustsec", + "repo": "advisory-db", + "type": "github" + } + }, "crane": { "inputs": { "flake-compat": "flake-compat", @@ -29,11 +45,11 @@ "rust-overlay": "rust-overlay" }, "locked": { - "lastModified": 1668993159, - "narHash": "sha256-9BVTtPFrHRh0HbeEm2bmXsoIWRj1tKM6Nvfl7VMK/X8=", + "lastModified": 1688772518, + "narHash": "sha256-ol7gZxwvgLnxNSZwFTDJJ49xVY5teaSvF7lzlo3YQfM=", "owner": "ipetkov", "repo": "crane", - "rev": "c61d98aaea5667607a36bafe5a6fa87fe5bb2c7e", + "rev": "8b08e96c9af8c6e3a2b69af5a7fa168750fcf88e", "type": "github" }, "original": { @@ -45,11 +61,11 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1650374568, - "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", "owner": "edolstra", "repo": "flake-compat", - "rev": "b4a34015c698c7793d592d66adbab377907a2be8", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", "type": "github" }, "original": { @@ -59,12 +75,15 @@ } }, "flake-utils": { + "inputs": { + "systems": "systems" + }, "locked": { - "lastModified": 1667395993, - "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", + "lastModified": 1687709756, + "narHash": "sha256-Y5wKlQSkgEK2weWdOu4J3riRd+kV/VCgHsqLNTTWQ/0=", "owner": "numtide", "repo": "flake-utils", - "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "rev": "dbabf0ca0c0c4bce6ea5eaf65af5cb694d2082c7", "type": "github" }, "original": { @@ -74,12 +93,15 @@ } }, "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, "locked": { - "lastModified": 1667395993, - "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", + "lastModified": 1689068808, + "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", "owner": "numtide", "repo": "flake-utils", - "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", "type": "github" }, "original": { @@ -90,11 +112,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1640319671, - "narHash": "sha256-ZkKmakwaOaLiZOpIZWbeJZwap5CzJ30s4UJTfydYIYc=", + "lastModified": 1678898370, + "narHash": "sha256-xTICr1j+uat5hk9FyuPOFGxpWHdJRibwZC+ATi0RbtE=", "owner": "nixos", "repo": "nixpkgs", - "rev": "eac07edbd20ed4908b98790ba299250b5527ecdf", + "rev": "ac718d02867a84b42522a0ece52d841188208f2c", "type": "github" }, "original": { @@ -106,11 +128,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1669507072, - "narHash": "sha256-RQAHgHM7wfgUTWbEjb8Ki43tQUUcwIg1Ra+PNustAVI=", + "lastModified": 1689631193, + "narHash": "sha256-AGSkBZaiTODQc8eT1rZDrQIjtb8JtFwJ0wVPzArlrnM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9e6b054555c9b10310543cd213ce7c70bb0cbc5f", + "rev": "57695599bdc4f7bfe5d28cfa23f14b3d8bdf8a5f", "type": "github" }, "original": { @@ -123,6 +145,7 @@ "root": { "inputs": { "Xess": "Xess", + "advisory-db": "advisory-db", "crane": "crane", "flake-utils": "flake-utils_2", "nixpkgs": "nixpkgs_2" @@ -140,11 +163,11 @@ ] }, "locked": { - "lastModified": 1667487142, - "narHash": "sha256-bVuzLs1ZVggJAbJmEDVO9G6p8BH3HRaolK70KXvnWnU=", + "lastModified": 1688351637, + "narHash": "sha256-CLTufJ29VxNOIZ8UTg0lepsn3X03AmopmaLTTeHDCL4=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "cf668f737ac986c0a89e83b6b2e3c5ddbd8cf33b", + "rev": "f9b92316727af9e6c7fee4a761242f7f46880329", "type": "github" }, "original": { @@ -153,13 +176,43 @@ "type": "github" } }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, "utils": { "locked": { - "lastModified": 1638122382, - "narHash": "sha256-sQzZzAbvKEqN9s0bzWuYmRaA03v40gaJ4+iL1LXjaeI=", + "lastModified": 1678901627, + "narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=", "owner": "numtide", "repo": "flake-utils", - "rev": "74f7e4319258e287b0f9cb95426c9853b282730b", + "rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 98bf3a1..afd1ecd 100644 --- a/flake.nix +++ b/flake.nix @@ -12,29 +12,41 @@ flake-utils.url = "github:numtide/flake-utils"; Xess.url = "github:Xe/Xess"; + + advisory-db = { + url = "github:rustsec/advisory-db"; + flake = false; + }; }; - outputs = { self, nixpkgs, crane, flake-utils, Xess, ... }: + outputs = { self, nixpkgs, crane, flake-utils, Xess, advisory-db, ... }: flake-utils.lib.eachSystem [ "x86_64-linux" "aarch64-linux" ] (system: let pkgs = import nixpkgs { inherit system; }; + inherit (pkgs) lib; + craneLib = crane.lib.${system}; - dref = craneLib.buildPackage { - src = craneLib.cleanCargoSource ./.; + src = craneLib.cleanCargoSource (craneLib.path ./.); + commonArgs = { + inherit src; buildInputs = [ - # Add additional build inputs here pkgs.openssl pkgs.pkg-config ] ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [ - # Additional darwin specific inputs can be set here pkgs.libiconv ]; }; + cargoArtifacts = craneLib.buildDepsOnly commonArgs; + + dref = craneLib.buildPackage (commonArgs // { + inherit cargoArtifacts; + }); + dockerImage = pkgs.dockerTools.buildImage { name = "dref"; config = { @@ -48,6 +60,35 @@ { checks = { inherit dref; + dref-clippy = craneLib.cargoClippy (commonArgs // { + inherit cargoArtifacts; + cargoClippyExtraArgs = "--all-targets -- --deny warnings"; + }); + + dref-doc = craneLib.cargoDoc (commonArgs // { + inherit cargoArtifacts; + }); + + dref-fmt = craneLib.cargoFmt { + inherit src; + }; + + dref-audit = craneLib.cargoAudit { + inherit src advisory-db; + }; + + # Run tests with cargo-nextest + # Consider setting `doCheck = false` on `dref` if you do not want + # the tests to run twice + dref-nextest = craneLib.cargoNextest (commonArgs // { + inherit cargoArtifacts; + partitions = 1; + partitionType = "count"; + }); + } // lib.optionalAttrs (system == "x86_64-linux") { + dref-coverage = craneLib.cargoTarpaulin (commonArgs // { + inherit cargoArtifacts; + }); }; apps.default = flake-utils.lib.mkApp { diff --git a/src/css.rs b/src/css.rs index 2334bcb..d7a69f4 100644 --- a/src/css.rs +++ b/src/css.rs @@ -1,4 +1,4 @@ -use std::{error::Error, path::Path, fs}; +use std::{error::Error, fs, path::Path}; const XESS: &str = r#" /* XESS by Xe https://github.com/Xe/Xess/blob/master/LICENSE */ @@ -138,9 +138,9 @@ hr { /// Returns either the plain XESS template with aoi theme or /// concats it with the contents of the theme.css file. pub fn render_css() -> Result> { - if Path::new("theme.css").exists() { - let theme: String = fs::read_to_string("theme.css")?.parse()?; - return Ok(format!("{}{}", XESS, theme).replace("\n", "")) - } - Ok(XESS.to_string().replace("\n", "")) + if Path::new("theme.css").exists() { + let theme: String = fs::read_to_string("theme.css")?.parse()?; + return Ok(format!("{}{}", XESS, theme).replace('\n', "")); + } + Ok(XESS.to_string().replace('\n', "")) } diff --git a/src/lib.rs b/src/lib.rs index 74fef46..3c51dd2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,64 +1,61 @@ +use serde::Deserialize; use std::collections::HashMap; use std::{boxed, error}; -use serde::{Deserialize}; #[derive(Clone)] pub struct Registry { - pub url: String, + pub url: String, } #[derive(Debug)] pub struct Image { - pub name: String, + pub name: String, } #[derive(Debug)] pub struct RichTag { - pub name: String, - architectures: Vec + pub name: String, } #[derive(Deserialize)] struct Tags { - name: String, - tags: Vec + tags: Vec, } impl Registry { - pub async fn images(&self) -> Result, boxed::Box> { - let resq_url = format!("{}/v2/_catalog", self.url); - let resp = reqwest::get(resq_url) - .await? - .json::>>() - .await?; - let i = resp.get("repositories").unwrap().into_iter().map(|i| async { - Image{ - name: i.to_string(), - } - }); - let images: Vec = futures::future::join_all(i).await; - Ok(images) - } - pub async fn image_tags(&self, image: String) -> Result, boxed::Box> { - let resq_url = format!("{}/v2/{}/tags/list", self.url, image); - let resp: Tags = reqwest::get(resq_url) - .await? - .json::() - .await?; - let i = resp.tags.into_iter().map(|i| async move { - RichTag{ - name: i.to_string(), - architectures: Vec::new(), - } - }); - let tags: Vec = futures::future::join_all(i).await; - Ok(tags) - } + pub async fn images(&self) -> Result, boxed::Box> { + let resq_url = format!("{}/v2/_catalog", self.url); + let resp = reqwest::get(resq_url) + .await? + .json::>>() + .await?; + let i = resp.get("repositories").unwrap().iter().map(|i| async { + Image { + name: i.to_string(), + } + }); + let images: Vec = futures::future::join_all(i).await; + Ok(images) + } + pub async fn image_tags( + &self, + image: String, + ) -> Result, boxed::Box> { + let resq_url = format!("{}/v2/{}/tags/list", self.url, image); + let resp: Tags = reqwest::get(resq_url).await?.json::().await?; + let i = resp.tags.iter().map(|i| async move { + RichTag { + name: i.to_string(), + } + }); + let tags: Vec = futures::future::join_all(i).await; + Ok(tags) + } } #[tokio::test] async fn test_registry() { - // Request a new server from the pool + // Request a new server from the pool let mut server = mockito::Server::new_async().await; // Use one of these addresses to configure your client @@ -66,23 +63,23 @@ async fn test_registry() { let url = server.url(); // Create a mock - let mock = server.mock("GET", "/v2/_catalog") - .with_status(201) - .with_header("content-type", "application/json") - .with_body(r#"{"repositories": ["dref"]}"#) - .create_async().await; + let mock = server + .mock("GET", "/v2/_catalog") + .with_status(201) + .with_header("content-type", "application/json") + .with_body(r#"{"repositories": ["dref"]}"#) + .create_async() + .await; - let registry = Registry{ - url, - }; + let registry = Registry { url }; - let response = registry.images().await; - let images = match response { - Ok(i) => i, - Err(_) => Vec::new(), - }; + let response = registry.images().await; + let images = match response { + Ok(i) => i, + Err(_) => Vec::new(), + }; - assert_eq!(images.first().unwrap().name, "dref"); + assert_eq!(images.first().unwrap().name, "dref"); - mock.assert(); + mock.assert(); } diff --git a/src/main.rs b/src/main.rs index 08b6b33..8a59fbc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,28 +1,28 @@ mod css; -use std::{boxed, error, env, collections::{HashMap, self}}; +use axum::{extract::State, response::IntoResponse, routing::get, Router}; use maud::{html, Markup, DOCTYPE}; -use axum::{Router, routing::get, response::IntoResponse, extract::State}; -use dref; +use std::env; #[derive(Clone)] struct AppState { - registry: dref::Registry + registry: dref::Registry, } #[tokio::main(flavor = "multi_thread", worker_threads = 10)] async fn main() { - let registry = dref::Registry{ - url: env::var("DREF_REGISTRY").unwrap(), - }; - let state = AppState {registry}; + let registry = dref::Registry { + url: env::var("DREF_REGISTRY").unwrap(), + }; + let state = AppState { registry }; // build our application with a single route let app = Router::new() - .route("/", get(root)).route("/styles.css", get(css)) - .with_state(state); + .route("/", get(root)) + .route("/styles.css", get(css)) + .with_state(state); // run it with hyper on localhost:3000 - println!("Running webserver on port :3000"); + println!("Running webserver on port :3000"); axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) .serve(app.into_make_service()) .await @@ -30,19 +30,22 @@ async fn main() { } async fn css() -> impl IntoResponse { - ([("Content-Type", "text/css")], match css::render_css() { - Ok(css) => css, - Err(_) => "".to_owned(), - }) + ( + [("Content-Type", "text/css")], + match css::render_css() { + Ok(css) => css, + Err(_) => "".to_owned(), + }, + ) } fn header(page_title: &str) -> Markup { - html! { - (DOCTYPE) - meta charset="utf-8"; + html! { + (DOCTYPE) + meta charset="utf-8"; title { (page_title) } - link rel="stylesheet" href="/styles.css"; - } + link rel="stylesheet" href="/styles.css"; + } } /// A static footer. @@ -53,27 +56,27 @@ fn footer() -> Markup { } async fn root(State(state): State) -> Markup { - let c = state.registry.images().await.unwrap(); + let c = state.registry.images().await.unwrap(); html! { - (header("/DREF")) - body { - main { - h1 { "/DREF" } - hr; - @for image in c { - div { - details { - summary { (image.name) } - ul { - @for tag in state.registry.image_tags(image.name).await.unwrap() { - li { (tag.name) } - } - } - } - } - } - } - } - (footer()) - } + (header("/DREF")) + body { + main { + h1 { "/DREF" } + hr; + @for image in c { + div { + details { + summary { (image.name) } + ul { + @for tag in state.registry.image_tags(image.name).await.unwrap() { + li { (tag.name) } + } + } + } + } + } + } + } + (footer()) + } }