agama/rust/agama-server/tests/server_service.rs
José Iván López González 46f4b158bc Use correct profile schema path
2026-06-25 13:39:39 +01:00

300 lines
8.9 KiB
Rust

// Copyright (c) [2025-2026] SUSE LLC
//
// 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 2 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, contact SUSE LLC.
//
// To contact SUSE LLC about this file by physical or electronic mail, you may
// find current contact information at www.suse.com.
pub mod common;
use agama_manager::test_utils::start_service;
use agama_server::{
profile::profile_service,
server::web::{server_with_state, ServerState},
};
use agama_utils::{
api::{event, status::Task, Event},
question, test,
};
use axum::http::{Method, Request, StatusCode};
use common::body_to_string;
use std::{error::Error, path::PathBuf};
use test_context::{test_context, AsyncTestContext};
use tokio::{sync::broadcast::channel, test};
use crate::common::Client;
struct Context {
client: Client,
events: event::Receiver,
}
impl AsyncTestContext for Context {
async fn setup() -> Context {
let share_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../test/share");
std::env::set_var("AGAMA_SHARE_DIR", share_dir.display().to_string());
let schema_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../share");
std::env::set_var("AGAMA_SCHEMA_DIR", schema_dir.display().to_string());
let (events_tx, events_rx) = channel(100);
let dbus = test::dbus::connection().await.unwrap();
let questions = question::start(events_tx.clone()).await.unwrap();
let manager = start_service(events_tx, dbus).await;
let profile = profile_service().await;
let service = server_with_state(ServerState::new(manager, questions), profile)
.expect("Could not create the testing router");
Context {
client: Client::new(service),
events: events_rx,
}
}
}
async fn select_product(client: &Client) -> Result<(), Box<dyn Error>> {
let json = r#"{"product": {"id": "SLES", "mode": "standard"}}"#;
let request = Request::builder()
.uri("/config")
.header("Content-Type", "application/json")
.method(Method::PUT)
.body(json.to_string())?;
let response = client.send_request(request).await;
assert_eq!(
response.status(),
StatusCode::OK,
"Failed to select the product"
);
Ok(())
}
#[test_context(Context)]
#[test]
async fn test_get_extended_config(ctx: &mut Context) -> Result<(), Box<dyn Error>> {
let request = Request::builder()
.uri("/extended_config")
.body("".to_string())
.unwrap();
let response = ctx.client.send_request(request).await;
assert_eq!(response.status(), StatusCode::OK);
let body = body_to_string(response.into_body()).await;
assert!(body.contains(r#""locale""#));
assert!(body.contains(r#""keymap""#));
assert!(body.contains(r#""timezone""#));
Ok(())
}
// Waits until the configuration is applied.
//
// NOTE: temporarily it waits for the "software_config" task to be completed.
// In the future we plan to add an specific event.
async fn wait_until_finished(events: &mut event::Receiver) {
const TASK_NAME: &str = "software_config";
loop {
match events.recv().await {
Ok(Event::TaskFinished {
task: Task { name, .. },
..
}) if name.as_str() == TASK_NAME => break,
Ok(_event) => {}
Err(error) => {
eprintln!("Error receiving events: {error}");
}
}
}
}
#[test_context(Context)]
#[test]
async fn test_get_empty_config(ctx: &mut Context) -> Result<(), Box<dyn Error>> {
let request = Request::builder()
.uri("/config")
.body("".to_string())
.unwrap();
let response = ctx.client.send_request(request).await;
assert_eq!(response.status(), StatusCode::OK);
let body = body_to_string(response.into_body()).await;
assert_eq!(&body, "{}");
Ok(())
}
#[test_context(Context)]
#[test]
async fn test_put_config_success(ctx: &mut Context) -> Result<(), Box<dyn Error>> {
let json = r#"
{
"product": { "id": "SLES", "mode": "standard" },
"l10n": {
"locale": "es_ES.UTF-8", "keymap": "es", "timezone": "Atlantic/Canary"
}
}
"#;
let request = Request::builder()
.uri("/config")
.header("Content-Type", "application/json")
.method(Method::PUT)
.body(json.to_string())
.unwrap();
let response = ctx.client.send_request(request).await;
assert_eq!(response.status(), StatusCode::OK);
wait_until_finished(&mut ctx.events).await;
let request = Request::builder()
.uri("/config")
.body("".to_string())
.unwrap();
let response = ctx.client.send_request(request).await;
assert_eq!(response.status(), StatusCode::OK);
let body = body_to_string(response.into_body()).await;
assert!(body
.contains(r#""l10n":{"locale":"es_ES.UTF-8","keymap":"es","timezone":"Atlantic/Canary"#));
Ok(())
}
#[test_context(Context)]
#[test]
async fn test_put_config_without_mode(ctx: &mut Context) -> Result<(), Box<dyn Error>> {
let json = r#"
{
"product": { "id": "SLES" },
"l10n": {
"locale": "es_ES.UTF-8", "keymap": "es", "timezone": "Atlantic/Canary"
}
}
"#;
let request = Request::builder()
.uri("/config")
.header("Content-Type", "application/json")
.method(Method::PUT)
.body(json.to_string())
.unwrap();
let response = ctx.client.send_request(request).await;
assert_eq!(response.status(), StatusCode::OK);
wait_until_finished(&mut ctx.events).await;
let request = Request::builder()
.uri("/extended_config")
.body("".to_string())
.unwrap();
let response = ctx.client.send_request(request).await;
assert_eq!(response.status(), StatusCode::OK);
let body = body_to_string(response.into_body()).await;
assert!(body.contains(r#""product":{"id":"SLES","mode":"standard"}"#));
Ok(())
}
#[test_context(Context)]
#[test]
async fn test_put_config_invalid_json(ctx: &mut Context) -> Result<(), Box<dyn Error>> {
let json = r#"{"key":"value"}"#;
let request = Request::builder()
.uri("/config")
.header("Content-Type", "application/json")
.method(Method::PUT)
.body(json.to_string())
.unwrap();
let response = ctx.client.send_request(request).await;
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
Ok(())
}
#[test_context(Context)]
#[test]
async fn test_patch_config_success(ctx: &mut Context) -> Result<(), Box<dyn Error>> {
select_product(&ctx.client).await?;
let json = r#"
{
"l10n": {
"locale": "es_ES.UTF-8", "keymap": "es", "timezone": "Atlantic/Canary"
}
}
"#;
let request = Request::builder()
.uri("/config")
.header("Content-Type", "application/json")
.method(Method::PATCH)
.body(json.to_string())
.unwrap();
let response = ctx.client.send_request(request).await;
assert_eq!(response.status(), StatusCode::OK);
wait_until_finished(&mut ctx.events).await;
let json = r#"{ "update": { "l10n": { "keymap": "en" } } }"#;
let request = Request::builder()
.uri("/config")
.header("Content-Type", "application/json")
.method(Method::PATCH)
.body(json.to_string())
.unwrap();
let response = ctx.client.send_request(request).await;
assert_eq!(response.status(), StatusCode::OK);
let request = Request::builder()
.uri("/config")
.body("".to_string())
.unwrap();
let response = ctx.client.send_request(request).await;
assert_eq!(response.status(), StatusCode::OK);
wait_until_finished(&mut ctx.events).await;
let body = body_to_string(response.into_body()).await;
assert!(body.contains(r#""l10n":{"keymap":"en"}"#));
assert!(body.contains(r#""product":{"id":"SLES","mode":"standard"}"#));
Ok(())
}
#[test_context(Context)]
#[test]
async fn test_patch_config_invalid_json(ctx: &mut Context) -> Result<(), Box<dyn Error>> {
let json = r#"{"update": {"key":"value"}}"#;
let request = Request::builder()
.uri("/config")
.header("Content-Type", "application/json")
.method(Method::PATCH)
.body(json.to_string())
.unwrap();
let response = ctx.client.send_request(request).await;
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
Ok(())
}