Menu
AkurAI-Build
publicLatest change e16ed955e37bc7f7b00de000a208ef91006cf60e - Adopt the AkurAI ID SSO contract and inject pipeline build context by AkurAI Build
use std::{fs, path::Path, process::Command};
use anyhow::{Context, Result, ensure};
use serde_json::json;
mod support;
use support::ProcessServer;
/// Spin up the AkurAI Build HTTP server on an ephemeral port and verify health,
/// security headers, and API contract.
#[test]
fn server_health_and_api_contract() -> Result<()> {
let work = tempfile::tempdir()?;
let data = work.path().join("data");
let key_file = work.path().join("key.bin");
let token_file = work.path().join("token.txt");
let webhook_secret_file = work.path().join("webhook.txt");
let repository = work.path().join("repository");
fs::create_dir(&repository)?;
git(&repository, &["init", "-b", "main"])?;
git(&repository, &["config", "user.name", "AkurAI Test"])?;
git(
&repository,
&["config", "user.email", "test@example.invalid"],
)?;
fs::write(repository.join("README.md"), "# Fixture repository\n")?;
fs::write(repository.join("lib.rs"), "pub fn fixture() {}\n")?;
git(&repository, &["add", "."])?;
git(&repository, &["commit", "-m", "Add fixture source"])?;
// Generate key.
run_cli(&data, &key_file, &["keygen", "--out", path(&key_file)?])?;
// Migrate database.
run_cli(&data, &key_file, &["migrate"])?;
// Write secret files with correct permissions.
write_secret(
&token_file,
"0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff",
)?;
write_secret(
&webhook_secret_file,
"aaaabbbbccccddddeeeeffff00001111aaaabbbbccccddddeeeeffff00001111",
)?;
let mut command = Command::new(env!("CARGO_BIN_EXE_akurai"));
command.args([
"--data",
path(&data)?,
"--key-file",
path(&key_file)?,
"serve",
"--address",
"127.0.0.1:0",
"--token-file",
path(&token_file)?,
"--webhook-secret-file",
path(&webhook_secret_file)?,
"--public-url",
"http://localhost",
"--allow-native",
"--worker-count",
"1",
]);
// Remove env vars that might override test config.
command.env_remove("AKURAI_DATA");
command.env_remove("AKURAI_KEY_FILE");
command.env_remove("AKURAI_ADDRESS");
command.env_remove("AKURAI_TOKEN_FILE");
command.env_remove("AKURAI_WEBHOOK_SECRET_FILE");
command.env_remove("AKURAI_PUBLIC_URL");
command.env_remove("AKURAI_ALLOW_NATIVE");
command.env_remove("AKURAI_WORKERS");
let mut server = ProcessServer::start_dynamic(command);
// Health endpoint (unauthenticated).
let health = server.request("GET", "/api/health");
health.assert_status(200);
health.assert_body_contains("\"ok\"");
// State endpoint requires auth.
let no_auth = server.request("GET", "/api/state");
no_auth.assert_status(401);
// State endpoint with auth.
let state = server.request_with_headers(
"GET",
"/api/state",
&[(
"Authorization",
"Bearer 0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff",
)],
);
state.assert_status(200);
state.assert_body_contains("\"ok\"");
state.assert_body_contains("\"repositories\"");
// Workers endpoint with auth.
let workers = server.request_with_headers(
"GET",
"/api/workers",
&[(
"Authorization",
"Bearer 0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff",
)],
);
workers.assert_status(200);
workers.assert_body_contains("\"ok\"");
// Landing page.
let landing = server.request("GET", "/");
landing.assert_status(200);
landing.assert_body_contains("AkurAI Build");
// Security headers on landing page.
landing.assert_security_headers();
// CSS asset served with correct content type.
let css = server.request("GET", "/assets/app.css");
css.assert_status(200);
css.assert_header_contains("content-type", "text/css");
// JS asset served.
let js = server.request("GET", "/assets/app.js");
js.assert_status(200);
js.assert_header_contains("content-type", "text/javascript");
// 404 page.
let not_found = server.request("GET", "/nonexistent/path");
assert!(
not_found.status == 404 || not_found.status == 200,
"should return 404 or landing page for unknown paths, got {}",
not_found.status
);
let add_body = serde_json::to_vec(&json!({
"name": "api-test",
"url": path(&repository)?,
"branch": "main"
}))?;
let add_repo = server.request_with_body(
"POST",
"/api/repos",
&[
(
"Authorization",
"Bearer 0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff",
),
("Content-Type", "application/json"),
],
&add_body,
);
add_repo.assert_status(200);
add_repo.assert_body_contains("\"api-test\"");
let tree = server.request_with_headers(
"GET",
"/api/repos/api-test/tree",
&[(
"Authorization",
"Bearer 0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff",
)],
);
tree.assert_status(200);
tree.assert_body_contains("README.md");
tree.assert_body_contains("\"recent_commits\"");
tree.assert_body_contains("\"contributors\"");
tree.assert_body_contains("\"languages\"");
// Repository list via state.
let state2 = server.request_with_headers(
"GET",
"/api/state",
&[(
"Authorization",
"Bearer 0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff",
)],
);
state2.assert_body_contains("api-test");
server.shutdown_with_sigterm();
Ok(())
}
fn write_secret(path: &Path, content: &str) -> Result<()> {
fs::write(path, content)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(path, fs::Permissions::from_mode(0o600))?;
}
Ok(())
}
fn run_cli(data: &Path, key: &Path, arguments: &[&str]) -> Result<()> {
let output = Command::new(env!("CARGO_BIN_EXE_akurai"))
.args(["--data", path(data)?, "--key-file", path(key)?])
.args(arguments)
.output()?;
anyhow::ensure!(
output.status.success(),
"CLI {} failed: {}",
arguments.join(" "),
String::from_utf8_lossy(&output.stderr)
);
Ok(())
}
fn git(directory: &Path, arguments: &[&str]) -> Result<()> {
let status = Command::new("git")
.current_dir(directory)
.args(arguments)
.status()?;
ensure!(status.success(), "git {} failed", arguments.join(" "));
Ok(())
}
fn path(path: &Path) -> Result<&str> {
path.to_str().context("test path is not UTF-8")
}