300 lines
8.9 KiB
Rust
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(())
|
|
}
|