Public Access
initial-commit
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
mod harness;
|
||||
|
||||
use charybdis::charybdis::core::{ComponentMetadata, ComponentSpec};
|
||||
use charybdis::charybdis::entities::Entity;
|
||||
use charybdis::charybdis::entities::entity::{Metadata, Spec};
|
||||
use harness::TestDb;
|
||||
|
||||
fn test_component(name: &str) -> Entity {
|
||||
Entity {
|
||||
kind: "Component".to_string(),
|
||||
metadata: Some(Metadata::ComponentMetadata(ComponentMetadata {
|
||||
name: name.to_string(),
|
||||
namespace: "default".to_string(),
|
||||
description: format!("{} service", name),
|
||||
..Default::default()
|
||||
})),
|
||||
spec: Some(Spec::ComponentSpec(ComponentSpec {
|
||||
r#type: "service".to_string(),
|
||||
lifecycle: "production".to_string(),
|
||||
owner: "team-platform".to_string(),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn create_and_get_entity() {
|
||||
let db = TestDb::new().await;
|
||||
|
||||
let created = db.repository.create(&test_component("payment-api")).await.unwrap();
|
||||
assert!(!created.id.is_empty());
|
||||
assert_eq!(created.kind, "Component");
|
||||
assert!(created.created_at.is_some());
|
||||
assert!(created.updated_at.is_some());
|
||||
|
||||
let fetched = db.repository.get_by_id(&created.id).await.unwrap().unwrap();
|
||||
assert_eq!(fetched.id, created.id);
|
||||
assert_eq!(fetched.kind, "Component");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_nonexistent_returns_none() {
|
||||
let db = TestDb::new().await;
|
||||
|
||||
let result = db
|
||||
.repository
|
||||
.get_by_id("00000000-0000-0000-0000-000000000000")
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(result.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_entity() {
|
||||
let db = TestDb::new().await;
|
||||
|
||||
let created = db.repository.create(&test_component("auth-svc")).await.unwrap();
|
||||
|
||||
let mut updated_data = test_component("auth-svc");
|
||||
if let Some(Metadata::ComponentMetadata(ref mut m)) = updated_data.metadata {
|
||||
m.description = "Updated auth service".to_string();
|
||||
}
|
||||
|
||||
let updated = db
|
||||
.repository
|
||||
.update(&created.id, &updated_data)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(updated.id, created.id);
|
||||
assert_eq!(updated.created_at, created.created_at);
|
||||
assert_ne!(updated.updated_at, created.updated_at);
|
||||
|
||||
if let Some(Metadata::ComponentMetadata(m)) = &updated.metadata {
|
||||
assert_eq!(m.description, "Updated auth service");
|
||||
} else {
|
||||
panic!("Expected ComponentMetadata");
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn delete_entity() {
|
||||
let db = TestDb::new().await;
|
||||
|
||||
let created = db.repository.create(&test_component("to-delete")).await.unwrap();
|
||||
let deleted = db.repository.delete(&created.id).await.unwrap();
|
||||
assert!(deleted);
|
||||
|
||||
let gone = db.repository.get_by_id(&created.id).await.unwrap();
|
||||
assert!(gone.is_none());
|
||||
|
||||
// Double delete returns false
|
||||
let deleted_again = db.repository.delete(&created.id).await.unwrap();
|
||||
assert!(!deleted_again);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn list_paginated() {
|
||||
let db = TestDb::new().await;
|
||||
|
||||
for i in 0..5 {
|
||||
db.repository
|
||||
.create(&test_component(&format!("svc-{}", i)))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Page 1: get 2 entities
|
||||
let page1 = db
|
||||
.repository
|
||||
.list_paginated(Some("Component"), None, 2, None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(page1.entities.len(), 2);
|
||||
assert_eq!(page1.total_count, 5);
|
||||
assert!(page1.next_page_token.is_some());
|
||||
|
||||
// Page 2: next 2
|
||||
let page2 = db
|
||||
.repository
|
||||
.list_paginated(Some("Component"), None, 2, page1.next_page_token.as_deref())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(page2.entities.len(), 2);
|
||||
assert!(page2.next_page_token.is_some());
|
||||
|
||||
// Page 3: last 1
|
||||
let page3 = db
|
||||
.repository
|
||||
.list_paginated(Some("Component"), None, 2, page2.next_page_token.as_deref())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(page3.entities.len(), 1);
|
||||
assert!(page3.next_page_token.is_none());
|
||||
|
||||
// No duplicates across pages
|
||||
let all_ids: Vec<String> = page1
|
||||
.entities
|
||||
.iter()
|
||||
.chain(page2.entities.iter())
|
||||
.chain(page3.entities.iter())
|
||||
.map(|e| e.id.clone())
|
||||
.collect();
|
||||
let unique: std::collections::HashSet<&String> = all_ids.iter().collect();
|
||||
assert_eq!(all_ids.len(), unique.len());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_by_kind_and_name() {
|
||||
let db = TestDb::new().await;
|
||||
|
||||
db.repository.create(&test_component("unique-svc")).await.unwrap();
|
||||
db.repository.create(&test_component("other-svc")).await.unwrap();
|
||||
|
||||
let found = db
|
||||
.repository
|
||||
.get_by_kind_and_name("Component", "unique-svc")
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(found.is_some());
|
||||
if let Some(Metadata::ComponentMetadata(m)) = &found.unwrap().metadata {
|
||||
assert_eq!(m.name, "unique-svc");
|
||||
}
|
||||
|
||||
let not_found = db
|
||||
.repository
|
||||
.get_by_kind_and_name("Component", "nonexistent")
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(not_found.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn atomic_annotation_update() {
|
||||
let db = TestDb::new().await;
|
||||
|
||||
let created = db.repository.create(&test_component("annotated-svc")).await.unwrap();
|
||||
|
||||
// First annotation update
|
||||
let mut annotations1 = std::collections::HashMap::new();
|
||||
annotations1.insert("defectdojo.com/product-id".to_string(), "123".to_string());
|
||||
let updated = db
|
||||
.repository
|
||||
.update_annotations(&created.id, annotations1)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(updated);
|
||||
|
||||
// Second annotation update (should merge, not overwrite)
|
||||
let mut annotations2 = std::collections::HashMap::new();
|
||||
annotations2.insert("github.com/repo".to_string(), "org/repo".to_string());
|
||||
db.repository
|
||||
.update_annotations(&created.id, annotations2)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Verify both annotations exist
|
||||
let entity = db.repository.get_by_id(&created.id).await.unwrap().unwrap();
|
||||
assert_eq!(entity.annotations.get("defectdojo.com/product-id").unwrap(), "123");
|
||||
assert_eq!(entity.annotations.get("github.com/repo").unwrap(), "org/repo");
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
use charybdis::database::{EntityRepository, ensure_schema};
|
||||
use sqlx::PgPool;
|
||||
use testcontainers::runners::AsyncRunner;
|
||||
use testcontainers_modules::postgres::Postgres;
|
||||
|
||||
pub struct TestDb {
|
||||
pub pool: PgPool,
|
||||
pub repository: EntityRepository,
|
||||
_container: testcontainers::ContainerAsync<Postgres>,
|
||||
}
|
||||
|
||||
impl TestDb {
|
||||
pub async fn new() -> Self {
|
||||
// Testcontainers picks up DOCKER_HOST or TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE
|
||||
// For Colima users: export DOCKER_HOST=unix:///Users/$USER/.colima/default/docker.sock
|
||||
let container = Postgres::default().start().await.unwrap();
|
||||
let port = container.get_host_port_ipv4(5432).await.unwrap();
|
||||
let url = format!("postgresql://postgres:postgres@127.0.0.1:{}/postgres", port);
|
||||
|
||||
let pool = PgPool::connect(&url).await.unwrap();
|
||||
ensure_schema(&pool).await.unwrap();
|
||||
|
||||
let repository = EntityRepository::new(pool.clone());
|
||||
|
||||
Self {
|
||||
pool,
|
||||
repository,
|
||||
_container: container,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user