Add basic tests
Some checks failed
Build Docker Image / nix-flake-check (push) Failing after 2m47s
Build Docker Image / arm-docker-build (push) Has been skipped

This commit is contained in:
Gabriel Simmer 2023-07-19 08:48:24 +01:00
parent 86bf19f068
commit 01e9d4818b
Signed by: arch
GPG key ID: C81B106D46C5B875
6 changed files with 246 additions and 132 deletions

View file

@ -5,7 +5,27 @@ on:
- trunk - trunk
jobs: 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: arm-docker-build:
needs: nix-flake-check
runs-on: debian-latest-arm runs-on: debian-latest-arm
steps: steps:
- name: Install prerequisites - name: Install prerequisites

View file

@ -6,11 +6,11 @@
"utils": "utils" "utils": "utils"
}, },
"locked": { "locked": {
"lastModified": 1667763126, "lastModified": 1682266158,
"narHash": "sha256-wKn3q3hICvEfs0m3CaeJmLCSyWVuVw/5LgDdh3QSXkU=", "narHash": "sha256-VK66KyF1doJ24yktb9rp7Yv7auS6i0P8EnJLhFOz+jY=",
"owner": "Xe", "owner": "Xe",
"repo": "Xess", "repo": "Xess",
"rev": "882799a25ce4fa4df014ad701aaa5a9b23ff9e85", "rev": "3a85d1de06cd3420b4d56a8edd72cd57e6f0806e",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -19,6 +19,22 @@
"type": "github" "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": { "crane": {
"inputs": { "inputs": {
"flake-compat": "flake-compat", "flake-compat": "flake-compat",
@ -29,11 +45,11 @@
"rust-overlay": "rust-overlay" "rust-overlay": "rust-overlay"
}, },
"locked": { "locked": {
"lastModified": 1668993159, "lastModified": 1688772518,
"narHash": "sha256-9BVTtPFrHRh0HbeEm2bmXsoIWRj1tKM6Nvfl7VMK/X8=", "narHash": "sha256-ol7gZxwvgLnxNSZwFTDJJ49xVY5teaSvF7lzlo3YQfM=",
"owner": "ipetkov", "owner": "ipetkov",
"repo": "crane", "repo": "crane",
"rev": "c61d98aaea5667607a36bafe5a6fa87fe5bb2c7e", "rev": "8b08e96c9af8c6e3a2b69af5a7fa168750fcf88e",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -45,11 +61,11 @@
"flake-compat": { "flake-compat": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1650374568, "lastModified": 1673956053,
"narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"owner": "edolstra", "owner": "edolstra",
"repo": "flake-compat", "repo": "flake-compat",
"rev": "b4a34015c698c7793d592d66adbab377907a2be8", "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -59,12 +75,15 @@
} }
}, },
"flake-utils": { "flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": { "locked": {
"lastModified": 1667395993, "lastModified": 1687709756,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", "narHash": "sha256-Y5wKlQSkgEK2weWdOu4J3riRd+kV/VCgHsqLNTTWQ/0=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", "rev": "dbabf0ca0c0c4bce6ea5eaf65af5cb694d2082c7",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -74,12 +93,15 @@
} }
}, },
"flake-utils_2": { "flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": { "locked": {
"lastModified": 1667395993, "lastModified": 1689068808,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -90,11 +112,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1640319671, "lastModified": 1678898370,
"narHash": "sha256-ZkKmakwaOaLiZOpIZWbeJZwap5CzJ30s4UJTfydYIYc=", "narHash": "sha256-xTICr1j+uat5hk9FyuPOFGxpWHdJRibwZC+ATi0RbtE=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "eac07edbd20ed4908b98790ba299250b5527ecdf", "rev": "ac718d02867a84b42522a0ece52d841188208f2c",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -106,11 +128,11 @@
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1669507072, "lastModified": 1689631193,
"narHash": "sha256-RQAHgHM7wfgUTWbEjb8Ki43tQUUcwIg1Ra+PNustAVI=", "narHash": "sha256-AGSkBZaiTODQc8eT1rZDrQIjtb8JtFwJ0wVPzArlrnM=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "9e6b054555c9b10310543cd213ce7c70bb0cbc5f", "rev": "57695599bdc4f7bfe5d28cfa23f14b3d8bdf8a5f",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -123,6 +145,7 @@
"root": { "root": {
"inputs": { "inputs": {
"Xess": "Xess", "Xess": "Xess",
"advisory-db": "advisory-db",
"crane": "crane", "crane": "crane",
"flake-utils": "flake-utils_2", "flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_2"
@ -140,11 +163,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1667487142, "lastModified": 1688351637,
"narHash": "sha256-bVuzLs1ZVggJAbJmEDVO9G6p8BH3HRaolK70KXvnWnU=", "narHash": "sha256-CLTufJ29VxNOIZ8UTg0lepsn3X03AmopmaLTTeHDCL4=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "cf668f737ac986c0a89e83b6b2e3c5ddbd8cf33b", "rev": "f9b92316727af9e6c7fee4a761242f7f46880329",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -153,13 +176,43 @@
"type": "github" "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": { "utils": {
"locked": { "locked": {
"lastModified": 1638122382, "lastModified": 1678901627,
"narHash": "sha256-sQzZzAbvKEqN9s0bzWuYmRaA03v40gaJ4+iL1LXjaeI=", "narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "74f7e4319258e287b0f9cb95426c9853b282730b", "rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -12,29 +12,41 @@
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
Xess.url = "github:Xe/Xess"; 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: flake-utils.lib.eachSystem [ "x86_64-linux" "aarch64-linux" ] (system:
let let
pkgs = import nixpkgs { pkgs = import nixpkgs {
inherit system; inherit system;
}; };
inherit (pkgs) lib;
craneLib = crane.lib.${system}; craneLib = crane.lib.${system};
dref = craneLib.buildPackage { src = craneLib.cleanCargoSource (craneLib.path ./.);
src = craneLib.cleanCargoSource ./.; commonArgs = {
inherit src;
buildInputs = [ buildInputs = [
# Add additional build inputs here
pkgs.openssl pkgs.openssl
pkgs.pkg-config pkgs.pkg-config
] ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [ ] ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [
# Additional darwin specific inputs can be set here
pkgs.libiconv pkgs.libiconv
]; ];
}; };
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
dref = craneLib.buildPackage (commonArgs // {
inherit cargoArtifacts;
});
dockerImage = pkgs.dockerTools.buildImage { dockerImage = pkgs.dockerTools.buildImage {
name = "dref"; name = "dref";
config = { config = {
@ -48,6 +60,35 @@
{ {
checks = { checks = {
inherit dref; 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 { apps.default = flake-utils.lib.mkApp {

View file

@ -1,4 +1,4 @@
use std::{error::Error, path::Path, fs}; use std::{error::Error, fs, path::Path};
const XESS: &str = r#" const XESS: &str = r#"
/* XESS by Xe https://github.com/Xe/Xess/blob/master/LICENSE */ /* 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 /// Returns either the plain XESS template with aoi theme or
/// concats it with the contents of the theme.css file. /// concats it with the contents of the theme.css file.
pub fn render_css() -> Result<String, Box<dyn Error>> { pub fn render_css() -> Result<String, Box<dyn Error>> {
if Path::new("theme.css").exists() { if Path::new("theme.css").exists() {
let theme: String = fs::read_to_string("theme.css")?.parse()?; let theme: String = fs::read_to_string("theme.css")?.parse()?;
return Ok(format!("{}{}", XESS, theme).replace("\n", "")) return Ok(format!("{}{}", XESS, theme).replace('\n', ""));
} }
Ok(XESS.to_string().replace("\n", "")) Ok(XESS.to_string().replace('\n', ""))
} }

View file

@ -1,64 +1,61 @@
use serde::Deserialize;
use std::collections::HashMap; use std::collections::HashMap;
use std::{boxed, error}; use std::{boxed, error};
use serde::{Deserialize};
#[derive(Clone)] #[derive(Clone)]
pub struct Registry { pub struct Registry {
pub url: String, pub url: String,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct Image { pub struct Image {
pub name: String, pub name: String,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct RichTag { pub struct RichTag {
pub name: String, pub name: String,
architectures: Vec<String>
} }
#[derive(Deserialize)] #[derive(Deserialize)]
struct Tags { struct Tags {
name: String, tags: Vec<String>,
tags: Vec<String>
} }
impl Registry { impl Registry {
pub async fn images(&self) -> Result<Vec<Image>, boxed::Box<dyn error::Error>> { pub async fn images(&self) -> Result<Vec<Image>, boxed::Box<dyn error::Error>> {
let resq_url = format!("{}/v2/_catalog", self.url); let resq_url = format!("{}/v2/_catalog", self.url);
let resp = reqwest::get(resq_url) let resp = reqwest::get(resq_url)
.await? .await?
.json::<HashMap<String, Vec<String>>>() .json::<HashMap<String, Vec<String>>>()
.await?; .await?;
let i = resp.get("repositories").unwrap().into_iter().map(|i| async { let i = resp.get("repositories").unwrap().iter().map(|i| async {
Image{ Image {
name: i.to_string(), name: i.to_string(),
} }
}); });
let images: Vec<Image> = futures::future::join_all(i).await; let images: Vec<Image> = futures::future::join_all(i).await;
Ok(images) Ok(images)
} }
pub async fn image_tags(&self, image: String) -> Result<Vec<RichTag>, boxed::Box<dyn error::Error>> { pub async fn image_tags(
let resq_url = format!("{}/v2/{}/tags/list", self.url, image); &self,
let resp: Tags = reqwest::get(resq_url) image: String,
.await? ) -> Result<Vec<RichTag>, boxed::Box<dyn error::Error>> {
.json::<Tags>() let resq_url = format!("{}/v2/{}/tags/list", self.url, image);
.await?; let resp: Tags = reqwest::get(resq_url).await?.json::<Tags>().await?;
let i = resp.tags.into_iter().map(|i| async move { let i = resp.tags.iter().map(|i| async move {
RichTag{ RichTag {
name: i.to_string(), name: i.to_string(),
architectures: Vec::new(), }
} });
}); let tags: Vec<RichTag> = futures::future::join_all(i).await;
let tags: Vec<RichTag> = futures::future::join_all(i).await; Ok(tags)
Ok(tags) }
}
} }
#[tokio::test] #[tokio::test]
async fn test_registry() { 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; let mut server = mockito::Server::new_async().await;
// Use one of these addresses to configure your client // Use one of these addresses to configure your client
@ -66,23 +63,23 @@ async fn test_registry() {
let url = server.url(); let url = server.url();
// Create a mock // Create a mock
let mock = server.mock("GET", "/v2/_catalog") let mock = server
.with_status(201) .mock("GET", "/v2/_catalog")
.with_header("content-type", "application/json") .with_status(201)
.with_body(r#"{"repositories": ["dref"]}"#) .with_header("content-type", "application/json")
.create_async().await; .with_body(r#"{"repositories": ["dref"]}"#)
.create_async()
.await;
let registry = Registry{ let registry = Registry { url };
url,
};
let response = registry.images().await; let response = registry.images().await;
let images = match response { let images = match response {
Ok(i) => i, Ok(i) => i,
Err(_) => Vec::new(), Err(_) => Vec::new(),
}; };
assert_eq!(images.first().unwrap().name, "dref"); assert_eq!(images.first().unwrap().name, "dref");
mock.assert(); mock.assert();
} }

View file

@ -1,28 +1,28 @@
mod css; 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 maud::{html, Markup, DOCTYPE};
use axum::{Router, routing::get, response::IntoResponse, extract::State}; use std::env;
use dref;
#[derive(Clone)] #[derive(Clone)]
struct AppState { struct AppState {
registry: dref::Registry registry: dref::Registry,
} }
#[tokio::main(flavor = "multi_thread", worker_threads = 10)] #[tokio::main(flavor = "multi_thread", worker_threads = 10)]
async fn main() { async fn main() {
let registry = dref::Registry{ let registry = dref::Registry {
url: env::var("DREF_REGISTRY").unwrap(), url: env::var("DREF_REGISTRY").unwrap(),
}; };
let state = AppState {registry}; let state = AppState { registry };
// build our application with a single route // build our application with a single route
let app = Router::new() let app = Router::new()
.route("/", get(root)).route("/styles.css", get(css)) .route("/", get(root))
.with_state(state); .route("/styles.css", get(css))
.with_state(state);
// run it with hyper on localhost:3000 // 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()) axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service()) .serve(app.into_make_service())
.await .await
@ -30,19 +30,22 @@ async fn main() {
} }
async fn css() -> impl IntoResponse { async fn css() -> impl IntoResponse {
([("Content-Type", "text/css")], match css::render_css() { (
Ok(css) => css, [("Content-Type", "text/css")],
Err(_) => "".to_owned(), match css::render_css() {
}) Ok(css) => css,
Err(_) => "".to_owned(),
},
)
} }
fn header(page_title: &str) -> Markup { fn header(page_title: &str) -> Markup {
html! { html! {
(DOCTYPE) (DOCTYPE)
meta charset="utf-8"; meta charset="utf-8";
title { (page_title) } title { (page_title) }
link rel="stylesheet" href="/styles.css"; link rel="stylesheet" href="/styles.css";
} }
} }
/// A static footer. /// A static footer.
@ -53,27 +56,27 @@ fn footer() -> Markup {
} }
async fn root(State(state): State<AppState>) -> Markup { async fn root(State(state): State<AppState>) -> Markup {
let c = state.registry.images().await.unwrap(); let c = state.registry.images().await.unwrap();
html! { html! {
(header("/DREF")) (header("/DREF"))
body { body {
main { main {
h1 { "/DREF" } h1 { "/DREF" }
hr; hr;
@for image in c { @for image in c {
div { div {
details { details {
summary { (image.name) } summary { (image.name) }
ul { ul {
@for tag in state.registry.image_tags(image.name).await.unwrap() { @for tag in state.registry.image_tags(image.name).await.unwrap() {
li { (tag.name) } li { (tag.name) }
} }
} }
} }
} }
} }
} }
} }
(footer()) (footer())
} }
} }