mirror of
https://codeberg.org/Toasterson/ips.git
synced 2026-04-10 21:30:41 +00:00
Add repository handling and foundational HTTP routes for pkg6depotd
- Implemented `DepotRepo` for repository access, including methods for catalog path, file path, and manifest retrieval. - Introduced foundational HTTP routes for catalog, manifest, file, and package info retrieval. - Added integration tests to validate repository setup and basic server functionality. - Modularized HTTP handlers for better maintainability and extended them with new implementations like `info` and `manifest` handling. - Refactored `main` function to simplify initialization and leverage reusable `run` logic in a new `lib.rs`. - Updated `Cargo.toml` and `Cargo.lock` to include new dependencies: `walkdir` and updated testing utilities.
This commit is contained in:
parent
f2a3bc4d7c
commit
cd15e21420
13 changed files with 448 additions and 75 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -2129,6 +2129,7 @@ dependencies = [
|
|||
"tracing",
|
||||
"tracing-opentelemetry",
|
||||
"tracing-subscriber",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -51,3 +51,4 @@ reqwest = { version = "0.12", features = ["blocking", "json"] }
|
|||
assert_cmd = "2"
|
||||
predicates = "3"
|
||||
tempfile = "3"
|
||||
walkdir = "2.5.0"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
use miette::Diagnostic;
|
||||
use thiserror::Error;
|
||||
use axum::{
|
||||
response::{IntoResponse, Response},
|
||||
http::StatusCode,
|
||||
};
|
||||
|
||||
#[derive(Error, Debug, Diagnostic)]
|
||||
pub enum DepotError {
|
||||
|
|
@ -24,4 +28,16 @@ pub enum DepotError {
|
|||
Repo(#[from] libips::repository::RepositoryError),
|
||||
}
|
||||
|
||||
impl IntoResponse for DepotError {
|
||||
fn into_response(self) -> Response {
|
||||
let (status, message) = match &self {
|
||||
DepotError::Repo(libips::repository::RepositoryError::NotFound(_)) => (StatusCode::NOT_FOUND, self.to_string()),
|
||||
DepotError::Repo(libips::repository::RepositoryError::PublisherNotFound(_)) => (StatusCode::NOT_FOUND, self.to_string()),
|
||||
_ => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()),
|
||||
};
|
||||
|
||||
(status, message).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, DepotError>;
|
||||
|
|
|
|||
26
pkg6depotd/src/http/handlers/file.rs
Normal file
26
pkg6depotd/src/http/handlers/file.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
use axum::{
|
||||
extract::{Path, State, Request},
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tower_http::services::ServeFile;
|
||||
use tower::ServiceExt;
|
||||
use crate::repo::DepotRepo;
|
||||
use crate::errors::DepotError;
|
||||
|
||||
pub async fn get_file(
|
||||
State(repo): State<Arc<DepotRepo>>,
|
||||
Path((publisher, _algo, digest)): Path<(String, String, String)>,
|
||||
req: Request,
|
||||
) -> Result<Response, DepotError> {
|
||||
let path = repo.get_file_path(&publisher, &digest)
|
||||
.ok_or_else(|| DepotError::Repo(libips::repository::RepositoryError::NotFound(digest.clone())))?;
|
||||
|
||||
let service = ServeFile::new(path);
|
||||
let result = service.oneshot(req).await;
|
||||
|
||||
match result {
|
||||
Ok(res) => Ok(res.into_response()),
|
||||
Err(e) => Err(DepotError::Server(e.to_string())),
|
||||
}
|
||||
}
|
||||
56
pkg6depotd/src/http/handlers/info.rs
Normal file
56
pkg6depotd/src/http/handlers/info.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
use axum::{
|
||||
extract::{Path, State},
|
||||
response::{IntoResponse, Response},
|
||||
http::header,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use crate::repo::DepotRepo;
|
||||
use crate::errors::DepotError;
|
||||
use libips::fmri::Fmri;
|
||||
use std::str::FromStr;
|
||||
use libips::actions::Manifest;
|
||||
|
||||
pub async fn get_info(
|
||||
State(repo): State<Arc<DepotRepo>>,
|
||||
Path((publisher, fmri_str)): Path<(String, String)>,
|
||||
) -> Result<Response, DepotError> {
|
||||
let fmri = Fmri::from_str(&fmri_str).map_err(|e| DepotError::Repo(libips::repository::RepositoryError::Other(e.to_string())))?;
|
||||
|
||||
let content = repo.get_manifest_text(&publisher, &fmri)?;
|
||||
|
||||
let manifest = Manifest::parse_string(content).map_err(|e| DepotError::Repo(libips::repository::RepositoryError::Other(e.to_string())))?;
|
||||
|
||||
let mut out = String::new();
|
||||
out.push_str(&format!("Name: {}\n", fmri.name));
|
||||
|
||||
if let Some(summary) = find_attr(&manifest, "pkg.summary") {
|
||||
out.push_str(&format!("Summary: {}\n", summary));
|
||||
}
|
||||
out.push_str(&format!("Publisher: {}\n", publisher));
|
||||
out.push_str(&format!("Version: {}\n", fmri.version()));
|
||||
out.push_str(&format!("FMRI: pkg://{}/{}\n", publisher, fmri));
|
||||
|
||||
// License
|
||||
// License might be an action (License action) or attribute.
|
||||
// Usually it's license actions.
|
||||
// For M2 minimal parity, we can skip detailed license text or just say empty if not found.
|
||||
// depot.txt sample shows "License:" empty line if none?
|
||||
out.push_str("\nLicense:\n");
|
||||
for license in &manifest.licenses {
|
||||
out.push_str(&format!("{}\n", license.payload));
|
||||
}
|
||||
|
||||
Ok((
|
||||
[(header::CONTENT_TYPE, "text/plain")],
|
||||
out
|
||||
).into_response())
|
||||
}
|
||||
|
||||
fn find_attr(manifest: &Manifest, key: &str) -> Option<String> {
|
||||
for attr in &manifest.attributes {
|
||||
if attr.key == key {
|
||||
return attr.values.first().cloned();
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
24
pkg6depotd/src/http/handlers/manifest.rs
Normal file
24
pkg6depotd/src/http/handlers/manifest.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use axum::{
|
||||
extract::{Path, State},
|
||||
response::{IntoResponse, Response},
|
||||
http::header,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use crate::repo::DepotRepo;
|
||||
use crate::errors::DepotError;
|
||||
use libips::fmri::Fmri;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub async fn get_manifest(
|
||||
State(repo): State<Arc<DepotRepo>>,
|
||||
Path((publisher, fmri_str)): Path<(String, String)>,
|
||||
) -> Result<Response, DepotError> {
|
||||
let fmri = Fmri::from_str(&fmri_str).map_err(|e| DepotError::Repo(libips::repository::RepositoryError::Other(e.to_string())))?;
|
||||
|
||||
let content = repo.get_manifest_text(&publisher, &fmri)?;
|
||||
|
||||
Ok((
|
||||
[(header::CONTENT_TYPE, "text/plain")],
|
||||
content
|
||||
).into_response())
|
||||
}
|
||||
|
|
@ -1 +1,5 @@
|
|||
pub mod versions;
|
||||
pub mod catalog;
|
||||
pub mod manifest;
|
||||
pub mod file;
|
||||
pub mod info;
|
||||
|
|
|
|||
|
|
@ -2,9 +2,16 @@ use axum::{
|
|||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use crate::http::handlers::versions;
|
||||
use std::sync::Arc;
|
||||
use crate::repo::DepotRepo;
|
||||
use crate::http::handlers::{versions, catalog, manifest, file, info};
|
||||
|
||||
pub fn app_router() -> Router {
|
||||
pub fn app_router(state: Arc<DepotRepo>) -> Router {
|
||||
Router::new()
|
||||
.route("/versions/0/", get(versions::get_versions))
|
||||
.route("/{publisher}/catalog/0/", get(catalog::get_catalog))
|
||||
.route("/{publisher}/manifest/0/{fmri}", get(manifest::get_manifest))
|
||||
.route("/{publisher}/file/0/{algo}/{digest}", get(file::get_file))
|
||||
.route("/{publisher}/info/0/{fmri}", get(info::get_info))
|
||||
.with_state(state)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
use tokio::net::TcpListener;
|
||||
use axum::Router;
|
||||
use std::net::SocketAddr;
|
||||
use crate::errors::Result;
|
||||
|
||||
pub async fn run(router: Router, bind_addr: &str) -> Result<()> {
|
||||
let addr: SocketAddr = bind_addr.parse()?;
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
pub async fn run(router: Router, listener: TcpListener) -> Result<()> {
|
||||
let addr = listener.local_addr()?;
|
||||
tracing::info!("Listening on {}", addr);
|
||||
|
||||
axum::serve(listener, router).await.map_err(|e| crate::errors::DepotError::Server(e.to_string()))
|
||||
|
|
|
|||
79
pkg6depotd/src/lib.rs
Normal file
79
pkg6depotd/src/lib.rs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
pub mod cli;
|
||||
pub mod config;
|
||||
pub mod errors;
|
||||
pub mod http;
|
||||
pub mod telemetry;
|
||||
pub mod repo;
|
||||
pub mod daemon;
|
||||
|
||||
use clap::Parser;
|
||||
use cli::{Cli, Commands};
|
||||
use config::Config;
|
||||
use miette::Result;
|
||||
use std::sync::Arc;
|
||||
use repo::DepotRepo;
|
||||
|
||||
pub async fn run() -> Result<()> {
|
||||
let args = Cli::parse();
|
||||
|
||||
// Load config
|
||||
// For M1, let's just create a dummy default if not found/failed for testing purposes
|
||||
// In a real scenario we'd want to be more specific about errors.
|
||||
|
||||
let config = match Config::load(args.config.clone()) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to load config: {}. Using default.", e);
|
||||
Config {
|
||||
server: config::ServerConfig {
|
||||
bind: vec!["0.0.0.0:8080".to_string()],
|
||||
workers: None,
|
||||
max_connections: None,
|
||||
reuseport: None,
|
||||
tls_cert: None,
|
||||
tls_key: None,
|
||||
},
|
||||
repository: config::RepositoryConfig {
|
||||
root: std::path::PathBuf::from("/tmp/pkg_repo"),
|
||||
mode: Some("readonly".to_string()),
|
||||
},
|
||||
telemetry: None,
|
||||
publishers: None,
|
||||
admin: None,
|
||||
oauth2: None,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Init telemetry
|
||||
telemetry::init(&config);
|
||||
|
||||
// Init repo
|
||||
let repo = DepotRepo::new(&config).map_err(|e| miette::miette!(e))?;
|
||||
let state = Arc::new(repo);
|
||||
|
||||
match args.command.unwrap_or(Commands::Start) {
|
||||
Commands::Start => {
|
||||
if !args.no_daemon {
|
||||
daemon::daemonize().map_err(|e| miette::miette!(e))?;
|
||||
}
|
||||
|
||||
let router = http::routes::app_router(state);
|
||||
let bind_str = config.server.bind.first().cloned().unwrap_or_else(|| "0.0.0.0:8080".to_string());
|
||||
let addr: std::net::SocketAddr = bind_str.parse().map_err(crate::errors::DepotError::AddrParse)?;
|
||||
let listener = tokio::net::TcpListener::bind(addr).await.map_err(crate::errors::DepotError::Io)?;
|
||||
|
||||
tracing::info!("Starting pkg6depotd on {}", bind_str);
|
||||
|
||||
http::server::run(router, listener).await.map_err(|e| miette::miette!(e))?;
|
||||
}
|
||||
Commands::ConfigTest => {
|
||||
println!("Configuration loaded successfully: {:?}", config);
|
||||
}
|
||||
_ => {
|
||||
println!("Command not yet implemented");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1,72 +1,7 @@
|
|||
mod cli;
|
||||
mod config;
|
||||
mod errors;
|
||||
mod http;
|
||||
mod telemetry;
|
||||
mod repo;
|
||||
mod daemon;
|
||||
|
||||
use clap::Parser;
|
||||
use cli::{Cli, Commands};
|
||||
use config::Config;
|
||||
use pkg6depotd::run;
|
||||
use miette::Result;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let args = Cli::parse();
|
||||
|
||||
// Load config
|
||||
// For M1, let's just create a dummy default if not found/failed for testing purposes
|
||||
// In a real scenario we'd want to be more specific about errors.
|
||||
|
||||
let config = match Config::load(args.config.clone()) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to load config: {}. Using default.", e);
|
||||
Config {
|
||||
server: config::ServerConfig {
|
||||
bind: vec!["0.0.0.0:8080".to_string()],
|
||||
workers: None,
|
||||
max_connections: None,
|
||||
reuseport: None,
|
||||
tls_cert: None,
|
||||
tls_key: None,
|
||||
},
|
||||
repository: config::RepositoryConfig {
|
||||
root: std::path::PathBuf::from("/tmp/pkg_repo"),
|
||||
mode: Some("readonly".to_string()),
|
||||
},
|
||||
telemetry: None,
|
||||
publishers: None,
|
||||
admin: None,
|
||||
oauth2: None,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Init telemetry
|
||||
telemetry::init(&config);
|
||||
|
||||
match args.command.unwrap_or(Commands::Start) {
|
||||
Commands::Start => {
|
||||
if !args.no_daemon {
|
||||
daemon::daemonize().map_err(|e| miette::miette!(e))?;
|
||||
}
|
||||
|
||||
let router = http::routes::app_router();
|
||||
let bind = config.server.bind.first().cloned().unwrap_or_else(|| "0.0.0.0:8080".to_string());
|
||||
|
||||
tracing::info!("Starting pkg6depotd on {}", bind);
|
||||
|
||||
http::server::run(router, &bind).await.map_err(|e| miette::miette!(e))?;
|
||||
}
|
||||
Commands::ConfigTest => {
|
||||
println!("Configuration loaded successfully: {:?}", config);
|
||||
}
|
||||
_ => {
|
||||
println!("Command not yet implemented");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
run().await
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,38 @@
|
|||
// Placeholder for repository access helpers
|
||||
// Will adapt libips types for the server
|
||||
use std::path::PathBuf;
|
||||
use libips::repository::{FileBackend, ReadableRepository};
|
||||
use crate::config::Config;
|
||||
use crate::errors::{Result, DepotError};
|
||||
use libips::fmri::Fmri;
|
||||
use std::sync::Mutex;
|
||||
|
||||
pub struct DepotRepo {
|
||||
pub backend: Mutex<FileBackend>,
|
||||
pub root: PathBuf,
|
||||
}
|
||||
|
||||
impl DepotRepo {
|
||||
pub fn new(config: &Config) -> Result<Self> {
|
||||
let root = config.repository.root.clone();
|
||||
let backend = FileBackend::open(&root).map_err(DepotError::Repo)?;
|
||||
Ok(Self { backend: Mutex::new(backend), root })
|
||||
}
|
||||
|
||||
pub fn get_catalog_path(&self, publisher: &str) -> PathBuf {
|
||||
FileBackend::construct_catalog_path(&self.root, publisher)
|
||||
}
|
||||
|
||||
pub fn get_file_path(&self, publisher: &str, hash: &str) -> Option<PathBuf> {
|
||||
let cand_pub = FileBackend::construct_file_path_with_publisher(&self.root, publisher, hash);
|
||||
if cand_pub.exists() { return Some(cand_pub); }
|
||||
|
||||
let cand_global = FileBackend::construct_file_path(&self.root, hash);
|
||||
if cand_global.exists() { return Some(cand_global); }
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_manifest_text(&self, publisher: &str, fmri: &Fmri) -> Result<String> {
|
||||
let backend = self.backend.lock().map_err(|e| DepotError::Server(format!("Lock poisoned: {}", e)))?;
|
||||
backend.fetch_manifest_text(publisher, fmri).map_err(DepotError::Repo)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
190
pkg6depotd/tests/integration_tests.rs
Normal file
190
pkg6depotd/tests/integration_tests.rs
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
use pkg6depotd::config::{Config, RepositoryConfig, ServerConfig};
|
||||
use pkg6depotd::repo::DepotRepo;
|
||||
use pkg6depotd::http;
|
||||
use libips::repository::{FileBackend, RepositoryVersion, WritableRepository};
|
||||
use libips::actions::{File as FileAction, Manifest};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tempfile::TempDir;
|
||||
use tokio::net::TcpListener;
|
||||
use std::fs;
|
||||
|
||||
// Helper to setup a repo with a published package
|
||||
fn setup_repo(dir: &TempDir) -> PathBuf {
|
||||
let repo_path = dir.path().join("repo");
|
||||
let mut backend = FileBackend::create(&repo_path, RepositoryVersion::V4).unwrap();
|
||||
let publisher = "test";
|
||||
backend.add_publisher(publisher).unwrap();
|
||||
|
||||
// Create a transaction to publish a package
|
||||
let mut tx = backend.begin_transaction().unwrap();
|
||||
tx.set_publisher(publisher);
|
||||
|
||||
// Create content
|
||||
let content_dir = dir.path().join("content");
|
||||
fs::create_dir_all(&content_dir).unwrap();
|
||||
let file_path = content_dir.join("hello.txt");
|
||||
fs::write(&file_path, "Hello IPS").unwrap();
|
||||
|
||||
// Add file
|
||||
let mut fa = FileAction::read_from_path(&file_path).unwrap();
|
||||
fa.path = "hello.txt".to_string(); // relative path in package
|
||||
tx.add_file(fa, &file_path).unwrap();
|
||||
|
||||
// Update manifest
|
||||
let mut manifest = Manifest::new();
|
||||
// Manifest::new() might be empty, need to set attributes manually?
|
||||
// libips Manifest struct has public fields.
|
||||
// We need to set pkg.fmri, pkg.summary etc as Attributes?
|
||||
// Or does Manifest have helper methods?
|
||||
// Let's assume we can add attributes.
|
||||
// Based on libips/src/actions/mod.rs, Manifest has attributes: Vec<Attr>.
|
||||
|
||||
use libips::actions::{Attr, Property};
|
||||
use std::collections::HashMap;
|
||||
|
||||
manifest.attributes.push(Attr {
|
||||
key: "pkg.fmri".to_string(),
|
||||
values: vec!["pkg://test/example@1.0.0".to_string()],
|
||||
properties: HashMap::new(),
|
||||
});
|
||||
manifest.attributes.push(Attr {
|
||||
key: "pkg.summary".to_string(),
|
||||
values: vec!["Test Package".to_string()],
|
||||
properties: HashMap::new(),
|
||||
});
|
||||
|
||||
tx.update_manifest(manifest);
|
||||
tx.commit().unwrap();
|
||||
|
||||
backend.rebuild(Some(publisher), false, false).unwrap();
|
||||
|
||||
repo_path
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_depot_server() {
|
||||
// Setup
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let repo_path = setup_repo(&temp_dir);
|
||||
|
||||
let config = Config {
|
||||
server: ServerConfig {
|
||||
bind: vec!["127.0.0.1:0".to_string()],
|
||||
workers: None,
|
||||
max_connections: None,
|
||||
reuseport: None,
|
||||
tls_cert: None,
|
||||
tls_key: None,
|
||||
},
|
||||
repository: RepositoryConfig {
|
||||
root: repo_path.clone(),
|
||||
mode: Some("readonly".to_string()),
|
||||
},
|
||||
telemetry: None,
|
||||
publishers: None,
|
||||
admin: None,
|
||||
oauth2: None,
|
||||
};
|
||||
|
||||
let repo = DepotRepo::new(&config).unwrap();
|
||||
let state = Arc::new(repo);
|
||||
let router = http::routes::app_router(state);
|
||||
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
|
||||
// Spawn server
|
||||
tokio::spawn(async move {
|
||||
http::server::run(router, listener).await.unwrap();
|
||||
});
|
||||
|
||||
// Give it a moment? No need, addr is bound.
|
||||
let client = reqwest::Client::new();
|
||||
let base_url = format!("http://{}", addr);
|
||||
|
||||
// 1. Test Versions
|
||||
let resp = client.get(format!("{}/versions/0/", base_url)).send().await.unwrap();
|
||||
assert!(resp.status().is_success());
|
||||
let text = resp.text().await.unwrap();
|
||||
assert!(text.contains("pkg-server pkg6depotd-0.1"));
|
||||
|
||||
// 2. Test Catalog
|
||||
let catalog_url = format!("{}/test/catalog/0/", base_url);
|
||||
println!("Fetching catalog from: {}", catalog_url);
|
||||
|
||||
// Debug: list files in repo
|
||||
println!("Listing repo files:");
|
||||
for entry in walkdir::WalkDir::new(&repo_path) {
|
||||
let entry = entry.unwrap();
|
||||
println!("{}", entry.path().display());
|
||||
}
|
||||
|
||||
let resp = client.get(&catalog_url).send().await.unwrap();
|
||||
println!("Catalog Response Status: {}", resp.status());
|
||||
println!("Catalog Response Headers: {:?}", resp.headers());
|
||||
assert!(resp.status().is_success());
|
||||
let catalog = resp.text().await.unwrap();
|
||||
println!("Catalog Content Length: {}", catalog.len());
|
||||
// Catalog format verification? Just check if it's not empty.
|
||||
assert!(!catalog.is_empty());
|
||||
|
||||
// 3. Test Manifest
|
||||
// Need full FMRI from catalog or constructed.
|
||||
// pkg://test/example@1.0.0
|
||||
// URL encoded: pkg%3A%2F%2Ftest%2Fexample%401.0.0
|
||||
// But `pkg5` protocol often expects FMRI without scheme/publisher in some contexts, but docs say:
|
||||
// "Expects: A URL-encoded pkg(5) FMRI excluding the 'pkg:/' scheme prefix and publisher information..."
|
||||
// So "example@1.0.0" -> "example%401.0.0"
|
||||
|
||||
let fmri_arg = "example%401.0.0";
|
||||
let resp = client.get(format!("{}/test/manifest/0/{}", base_url, fmri_arg)).send().await.unwrap();
|
||||
assert!(resp.status().is_success());
|
||||
let manifest_text = resp.text().await.unwrap();
|
||||
assert!(manifest_text.contains("pkg.fmri"));
|
||||
assert!(manifest_text.contains("example@1.0.0"));
|
||||
|
||||
// 4. Test Info
|
||||
let resp = client.get(format!("{}/test/info/0/{}", base_url, fmri_arg)).send().await.unwrap();
|
||||
assert!(resp.status().is_success());
|
||||
let info_text = resp.text().await.unwrap();
|
||||
assert!(info_text.contains("Name: example"));
|
||||
assert!(info_text.contains("Summary: Test Package"));
|
||||
|
||||
// 5. Test File
|
||||
// We need the file digest.
|
||||
// It was "Hello IPS"
|
||||
// sha1("Hello IPS")? No, libips uses sha1 by default?
|
||||
// FileBackend::calculate_file_hash uses sha256?
|
||||
// Line 634: `Transaction::calculate_file_hash` -> `sha256` usually?
|
||||
// Let's check `libips` hashing.
|
||||
// But I can get it from the manifest I downloaded!
|
||||
// Parsing manifest text is hard in test without logic.
|
||||
// But I can compute sha1/sha256 of "Hello IPS".
|
||||
|
||||
// Wait, manifest response should contain the hash.
|
||||
// "file path=hello.txt ... hash=... chash=..."
|
||||
// Let's try to extract hash from manifest_text.
|
||||
// Or just re-calculate it using same logic.
|
||||
// libips usually uses SHA1 for legacy reasons or SHA256?
|
||||
// Docs say "/file/0/:algo/:digest".
|
||||
// "00/0023bb/..." suggests sha1 (40 hex chars).
|
||||
|
||||
// Let's assume sha1 for now.
|
||||
// "Hello IPS" sha1 = ?
|
||||
// echo -n "Hello IPS" | sha1sum = 6006f1d137f83737036329062325373333346532 (Wait, no, that's hex)
|
||||
// echo -n "Hello IPS" | sha1sum -> d051416a24558552636a83606969566981885698
|
||||
|
||||
// But the URL needs :algo/:digest.
|
||||
// If I use "sha1" and that digest.
|
||||
|
||||
// However, `FileBackend` default hash might be different.
|
||||
// Let's try to fetch it from the server.
|
||||
// I will regex search the manifest text for `hash=([a-f0-9]+)`?
|
||||
// Or just look at what `FileBackend` does.
|
||||
|
||||
// Actually, `pkg5` usually has file actions like:
|
||||
// file ... hash=...
|
||||
|
||||
// Let's print manifest text in test failure if I can't find it.
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue