relay-knowledge 1.1.17

Graph-database-based knowledge graph project.
Documentation
//! Integration tests for file operations through the assembled Web router.

use axum::{
    body::{Body, to_bytes},
    http::{Request, StatusCode, header},
};
use serde_json::{Value, json};
use std::{
    path::{Path, PathBuf},
    time::{SystemTime, UNIX_EPOCH},
};
use tower::ServiceExt;

use crate::{
    application::RelayKnowledgeService,
    env::{EnvironmentConfig, PlatformKind},
};

use super::router;

#[tokio::test]
async fn executes_file_content_operation_through_web_dispatch() {
    let root = unique_temp_dir("execute-files-content-root");
    std::fs::write(root.join("runbook.md"), "service depends on database")
        .expect("file fixture should be written");
    let service = test_service_with_file_root("execute-files-content", &root).await;

    let index = execute_json(
        service.clone(),
        json!({
            "snapshot": {
                "name": "Index local files",
                "command": "relay-knowledge files index",
                "payload": {
                    "operation": "files.index",
                    "source_scope": "local-files",
                    "roots": []
                }
            }
        }),
        StatusCode::OK,
    )
    .await;
    assert_eq!(index["operation"], "files.index");

    let content = execute_json(
        service,
        json!({
            "snapshot": {
                "name": "Search local file content",
                "command": "relay-knowledge files content database",
                "payload": {
                    "operation": "files.content",
                    "query": "database",
                    "source_scope": "local-files",
                    "freshness": "allow-stale",
                    "limit": 5
                }
            }
        }),
        StatusCode::OK,
    )
    .await;

    assert_eq!(content["operation"], "files.content");
    assert_eq!(
        content["result"]["results"][0]["content_role"],
        "user_source"
    );
    assert!(
        content["result"]["results"][0]["path"]
            .as_str()
            .expect("path should be a string")
            .ends_with("runbook.md")
    );
}

async fn test_service_with_file_root(label: &str, root: &Path) -> RelayKnowledgeService {
    let home = unique_temp_dir(label);
    let environment = EnvironmentConfig::from_pairs(
        PlatformKind::Unix,
        [
            ("HOME", "/tmp"),
            (
                "RELAY_KNOWLEDGE_HOME",
                home.as_path().to_str().expect("utf8 path"),
            ),
            (
                "RELAY_KNOWLEDGE_FILE_INDEX_ROOTS",
                root.to_str().expect("utf8 root path"),
            ),
        ],
    )
    .expect("environment should parse");

    RelayKnowledgeService::from_environment(&environment)
        .await
        .expect("service should initialize")
}

async fn execute_json(
    service: RelayKnowledgeService,
    body: Value,
    expected_status: StatusCode,
) -> Value {
    let router = router(service, crate::net::http::DEFAULT_MAX_BODY_BYTES);
    let response = router
        .oneshot(
            Request::builder()
                .method("POST")
                .uri("/api/web/operations/execute")
                .header(header::CONTENT_TYPE, "application/json")
                .body(Body::from(body.to_string()))
                .expect("request should build"),
        )
        .await
        .expect("router should respond");
    assert_eq!(response.status(), expected_status);

    serde_json::from_str(&response_text(response).await).expect("response should be json")
}

async fn response_text(response: axum::response::Response) -> String {
    let bytes = to_bytes(response.into_body(), usize::MAX)
        .await
        .expect("response body should be readable");

    String::from_utf8(bytes.to_vec()).expect("response should be utf8")
}

fn unique_temp_dir(label: &str) -> PathBuf {
    let suffix = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("system time should be valid")
        .as_nanos();
    let root = std::env::temp_dir().join(format!(
        "relay-knowledge-web-files-{label}-{}-{suffix}",
        std::process::id()
    ));
    std::fs::create_dir_all(&root).expect("temp dir should be created");
    root
}