use std::collections::BTreeMap;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use kcode_dev_tools::{
CREATE_RUST_BIN_TOOL, CREATE_RUST_LIB_TOOL, CREATE_WEB_LIB_TOOL, Config, DOCS_RUST_BIN_TOOL,
DOCS_RUST_LIB_TOOL, DOCS_WEB_LIB_TOOL, ManagedSourceKind, ObjectStore, ObjectStoreError,
ObjectStoreResult, Service, WRITE_FILE_FREEFORM_RUST_BIN_TOOL,
WRITE_FILE_FREEFORM_RUST_LIB_TOOL, WRITE_FILE_FREEFORM_WEB_LIB_TOOL,
};
use serde_json::json;
static TEST_COUNTER: AtomicU64 = AtomicU64::new(0);
#[derive(Default)]
struct MemoryObjects {
objects: Mutex<BTreeMap<String, Vec<u8>>>,
}
impl ObjectStore for MemoryObjects {
fn load(&self, object_id: &str) -> ObjectStoreResult<Vec<u8>> {
self.objects
.lock()
.unwrap()
.get(object_id)
.cloned()
.ok_or_else(|| ObjectStoreError::new("not found"))
}
fn save(&self, bytes: &[u8]) -> ObjectStoreResult<String> {
let mut objects = self.objects.lock().unwrap();
let id = format!("object-{}", objects.len());
objects.insert(id.clone(), bytes.to_vec());
Ok(id)
}
}
struct TestRoot(PathBuf);
impl TestRoot {
fn new() -> Self {
let counter = TEST_COUNTER.fetch_add(1, Ordering::Relaxed);
let path = std::env::temp_dir().join(format!(
"kcode-dev-tools-integration-{}-{counter}",
std::process::id()
));
fs::create_dir(&path).unwrap();
Self(path)
}
fn path(&self) -> &Path {
&self.0
}
}
impl Drop for TestRoot {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.0);
}
}
fn service(root: &Path) -> Service {
Service::open(Config {
rust_libraries_root: root.join("rust-libs"),
web_libraries_root: root.join("web-libs"),
web_publications_root: root.join("web-publications"),
rust_binaries_root: root.join("rust-bins"),
rust_binary_publications_root: root.join("rust-bin-publications"),
crates_io_registry_token: "test-token".to_owned(),
object_store: Arc::new(MemoryObjects::default()),
})
.unwrap()
}
#[tokio::test]
async fn one_service_manages_all_three_source_kinds_and_releases_together() {
let root = TestRoot::new();
let service = service(root.path());
assert_eq!(
service.web_publications_root(),
fs::canonicalize(root.path().join("web-publications")).unwrap()
);
let session = "conversation:test";
for (create, write, docs, name, path, kind) in [
(
CREATE_RUST_LIB_TOOL,
WRITE_FILE_FREEFORM_RUST_LIB_TOOL,
DOCS_RUST_LIB_TOOL,
"demo-lib",
"src/extra.rs",
ManagedSourceKind::RustLibrary,
),
(
CREATE_WEB_LIB_TOOL,
WRITE_FILE_FREEFORM_WEB_LIB_TOOL,
DOCS_WEB_LIB_TOOL,
"demo-web",
"extra.js",
ManagedSourceKind::WebLibrary,
),
(
CREATE_RUST_BIN_TOOL,
WRITE_FILE_FREEFORM_RUST_BIN_TOOL,
DOCS_RUST_BIN_TOOL,
"demo-bin",
"src/extra.rs",
ManagedSourceKind::RustBinary,
),
] {
let created = service
.execute(session, create, json!({"name":name}))
.await
.unwrap();
assert_eq!(created.snapshot.as_ref().unwrap().kind, kind);
let written = service
.execute(
session,
write,
json!({"name":name,"path":path,"contents":"// managed\n"}),
)
.await
.unwrap();
assert!(written.snapshot.unwrap().text.contains(path));
let documentation = service
.execute(session, docs, json!({"name":name}))
.await
.unwrap();
assert!(documentation.text.contains("Version: 0.1.0"));
}
assert_eq!(service.release(session).await.unwrap(), 3);
}
#[test]
fn web_source_and_publication_roots_must_be_disjoint() {
let root = TestRoot::new();
let shared = root.path().join("web");
let error = match Service::open(Config {
rust_libraries_root: root.path().join("rust-libs"),
web_libraries_root: shared.clone(),
web_publications_root: shared,
rust_binaries_root: root.path().join("rust-bins"),
rust_binary_publications_root: root.path().join("rust-bin-publications"),
crates_io_registry_token: "test-token".to_owned(),
object_store: Arc::new(MemoryObjects::default()),
}) {
Ok(_) => panic!("overlapping Web roots were accepted"),
Err(error) => error,
};
assert_eq!(error.code, "dev_tools_invalid_root");
assert_eq!(
error.message,
"Managed Web source and publication roots must be disjoint."
);
}