Skip to content

Commit 51e0aae

Browse files
authored
test: expand integration scenario coverage (#42)
* test: expand integration scenario coverage * test: add databases_lifecycle scenario * fix(test): use service provider in embedding_providers_crud * test: add managed_tables_lifecycle scenario * test: poll get_table_profile for async sync in managed_tables_lifecycle * test: refresh-then-profile in managed_tables_lifecycle * test: drop managed_tables_lifecycle pending scenario-definition fix * test: add managed_tables_lifecycle (corrected per fixed scenario)
1 parent 7bdef69 commit 51e0aae

12 files changed

Lines changed: 675 additions & 0 deletions

tests/common/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,19 @@ pub async fn shared_database_id(client: &Client) -> String {
173173
.expect("create_database should succeed");
174174
created.id
175175
}
176+
177+
/// Create a fresh, uniquely-named `sdkci-*` database and return its id.
178+
///
179+
/// Unlike [`shared_database_id`], this is for scenarios that exercise the
180+
/// database lifecycle itself (contexts, catalog attach/detach) and tear the
181+
/// database back down at the end. The `sdkci-` prefix keeps any leak
182+
/// identifiable to the nightly sweep if the test panics before cleanup.
183+
pub async fn create_scratch_database(client: &Client, scenario: &str) -> String {
184+
use hotdata::apis::databases_api;
185+
let mut request = hotdata::models::CreateDatabaseRequest::new();
186+
request.name = Some(Some(sdkci_name(scenario)));
187+
let created = databases_api::create_database(client.configuration(), request)
188+
.await
189+
.expect("create_database should succeed");
190+
created.id
191+
}

tests/connection_types_read.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//! Scenario: connection_types_read.
2+
//!
3+
//! Defined in www.hotdata.dev/api/test-scenarios.yaml — read-only. list_connection_types
4+
//! returns the connector catalog, and get_connection_type fetches one by name
5+
//! with its config schema. No fixtures created.
6+
7+
mod common;
8+
9+
use hotdata::apis::connection_types_api;
10+
11+
#[tokio::test]
12+
async fn connection_types_read() {
13+
let client = skip_if_no_creds!();
14+
let config = client.configuration();
15+
16+
let listing = connection_types_api::list_connection_types(config)
17+
.await
18+
.expect("list_connection_types should succeed");
19+
assert!(
20+
!listing.connection_types.is_empty(),
21+
"expected a non-empty connector catalog"
22+
);
23+
for ct in &listing.connection_types {
24+
assert!(!ct.name.is_empty(), "connection type missing a name");
25+
assert!(
26+
!ct.label.is_empty(),
27+
"connection type {} missing a label",
28+
ct.name
29+
);
30+
}
31+
32+
// Fetch one by name and confirm the detail echoes the catalog entry.
33+
let first = &listing.connection_types[0];
34+
let detail = connection_types_api::get_connection_type(config, &first.name)
35+
.await
36+
.expect("get_connection_type should succeed");
37+
assert_eq!(detail.name, first.name);
38+
assert_eq!(detail.label, first.label);
39+
40+
// A fabricated connector name must not resolve to a real type.
41+
let bogus = connection_types_api::get_connection_type(config, "sdkci-not-a-connector").await;
42+
match bogus {
43+
Err(err) => assert_eq!(
44+
common::status_of(&err),
45+
Some(404),
46+
"expected 404 for an unknown connection type, got {err:?}"
47+
),
48+
Ok(_) => panic!("get_connection_type should 404 for an unknown connector name"),
49+
}
50+
}

tests/database_catalogs_attach.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//! Scenario: database_catalogs_attach.
2+
//!
3+
//! Defined in www.hotdata.dev/api/test-scenarios.yaml — create a scratch
4+
//! database, attach the seeded connection as a catalog (with an alias), confirm
5+
//! it's reachable via the database's attachments, then detach it and delete the
6+
//! database. Reversible and idempotent — never mutates the connection itself.
7+
8+
mod common;
9+
10+
use hotdata::apis::databases_api;
11+
use hotdata::models;
12+
13+
#[tokio::test]
14+
async fn database_catalogs_attach() {
15+
let (client, connection_id) = skip_if_no_connection!();
16+
let config = client.configuration();
17+
18+
let database_id = common::create_scratch_database(&client, "dbcat-attach").await;
19+
let alias = "sdkci_catalog";
20+
21+
let mut attach = models::AttachDatabaseCatalogRequest::new(connection_id.clone());
22+
attach.alias = Some(Some(alias.to_string()));
23+
databases_api::attach_database_catalog(config, &database_id, attach)
24+
.await
25+
.expect("attach_database_catalog should succeed");
26+
27+
let attached = databases_api::get_database(config, &database_id)
28+
.await
29+
.expect("get_database should succeed after attach");
30+
let found = attached
31+
.attachments
32+
.iter()
33+
.find(|a| a.connection_id == connection_id);
34+
let found = found.unwrap_or_else(|| {
35+
panic!("attached connection {connection_id} not present in database attachments")
36+
});
37+
assert_eq!(
38+
found.alias.as_ref().and_then(|a| a.as_deref()),
39+
Some(alias),
40+
"attachment alias should round-trip"
41+
);
42+
43+
databases_api::detach_database_catalog(config, &database_id, &connection_id)
44+
.await
45+
.expect("detach_database_catalog should succeed");
46+
47+
let detached = databases_api::get_database(config, &database_id)
48+
.await
49+
.expect("get_database should succeed after detach");
50+
assert!(
51+
!detached
52+
.attachments
53+
.iter()
54+
.any(|a| a.connection_id == connection_id),
55+
"connection {connection_id} should be gone from attachments after detach"
56+
);
57+
58+
// Tear down the scratch database.
59+
databases_api::delete_database(config, &database_id)
60+
.await
61+
.expect("delete_database should succeed");
62+
}

tests/database_contexts_crud.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//! Scenario: database_contexts_crud.
2+
//!
3+
//! Defined in www.hotdata.dev/api/test-scenarios.yaml — create a scratch
4+
//! database, upsert a named context document, read it back, confirm it appears
5+
//! in list_database_contexts, delete the context, then delete the database.
6+
//! Upserting the same name twice verifies replace-on-write.
7+
8+
mod common;
9+
10+
use hotdata::apis::{database_context_api, databases_api};
11+
use hotdata::models;
12+
13+
#[tokio::test]
14+
async fn database_contexts_crud() {
15+
let client = skip_if_no_creds!();
16+
let config = client.configuration();
17+
18+
let database_id = common::create_scratch_database(&client, "dbctx-crud").await;
19+
let context_name = "sdkci_context";
20+
21+
// Initial upsert.
22+
let initial = database_context_api::upsert_database_context(
23+
config,
24+
&database_id,
25+
models::UpsertDatabaseContextRequest::new(
26+
"first revision".to_string(),
27+
context_name.to_string(),
28+
),
29+
)
30+
.await
31+
.expect("upsert_database_context (create) should succeed");
32+
assert_eq!(initial.context.name, context_name);
33+
assert_eq!(initial.context.content, "first revision");
34+
35+
// Upsert the same name again — replace-on-write, not a duplicate.
36+
database_context_api::upsert_database_context(
37+
config,
38+
&database_id,
39+
models::UpsertDatabaseContextRequest::new(
40+
"second revision".to_string(),
41+
context_name.to_string(),
42+
),
43+
)
44+
.await
45+
.expect("upsert_database_context (replace) should succeed");
46+
47+
let fetched = database_context_api::get_database_context(config, &database_id, context_name)
48+
.await
49+
.expect("get_database_context should succeed");
50+
assert_eq!(fetched.context.name, context_name);
51+
assert_eq!(
52+
fetched.context.content, "second revision",
53+
"upsert with an existing name should replace the content"
54+
);
55+
56+
let listing = database_context_api::list_database_contexts(config, &database_id)
57+
.await
58+
.expect("list_database_contexts should succeed");
59+
let matches: Vec<_> = listing
60+
.contexts
61+
.iter()
62+
.filter(|c| c.name == context_name)
63+
.collect();
64+
assert_eq!(
65+
matches.len(),
66+
1,
67+
"expected exactly one context named {context_name} (replace, not append), got {}",
68+
matches.len()
69+
);
70+
71+
database_context_api::delete_database_context(config, &database_id, context_name)
72+
.await
73+
.expect("delete_database_context should succeed");
74+
75+
let after_delete =
76+
database_context_api::get_database_context(config, &database_id, context_name).await;
77+
match after_delete {
78+
Err(err) => assert_eq!(
79+
common::status_of(&err),
80+
Some(404),
81+
"expected 404 after context delete, got {err:?}"
82+
),
83+
Ok(_) => panic!("get_database_context should fail with 404 after delete"),
84+
}
85+
86+
// Tear down the scratch database.
87+
databases_api::delete_database(config, &database_id)
88+
.await
89+
.expect("delete_database should succeed");
90+
}

tests/databases_lifecycle.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
//! Scenario: databases_lifecycle.
2+
//!
3+
//! Defined in www.hotdata.dev/api/test-scenarios.yaml — create a database
4+
//! (metadata-only grouping), read it, confirm it appears in list_databases,
5+
//! declare a schema and a table on its managed catalog, then delete it and
6+
//! verify it's gone.
7+
8+
mod common;
9+
10+
use hotdata::apis::databases_api;
11+
use hotdata::models;
12+
13+
#[tokio::test]
14+
async fn databases_lifecycle() {
15+
let client = skip_if_no_creds!();
16+
let config = client.configuration();
17+
18+
// A uniquely-named sdkci-* database we create and tear down here.
19+
let database_id = common::create_scratch_database(&client, "databases-lifecycle").await;
20+
21+
let fetched = databases_api::get_database(config, &database_id)
22+
.await
23+
.expect("get_database should succeed");
24+
assert_eq!(fetched.id, database_id);
25+
26+
let listing = databases_api::list_databases(config)
27+
.await
28+
.expect("list_databases should succeed");
29+
assert!(
30+
listing.databases.iter().any(|d| d.id == database_id),
31+
"created database {database_id} not present in list_databases"
32+
);
33+
34+
// Declare a schema on the database's managed catalog. Identifiers are
35+
// SQL-scoped, so use underscore-only names (the database is unique per run,
36+
// so a fixed schema/table name can't collide across tests).
37+
let schema_name = "sdkci_schema";
38+
let schema = databases_api::add_database_schema(
39+
config,
40+
&database_id,
41+
models::AddManagedSchemaRequest::new(schema_name.to_string()),
42+
)
43+
.await
44+
.expect("add_database_schema should succeed");
45+
assert_eq!(schema.schema, schema_name);
46+
assert!(
47+
!schema.connection_id.is_empty(),
48+
"schema should report its managed-catalog connection"
49+
);
50+
51+
// Declare a table on that schema.
52+
let table_name = "sdkci_table";
53+
let table = databases_api::add_database_table(
54+
config,
55+
&database_id,
56+
schema_name,
57+
models::AddManagedTableRequest::new(table_name.to_string()),
58+
)
59+
.await
60+
.expect("add_database_table should succeed");
61+
assert_eq!(table.table, table_name);
62+
assert_eq!(table.schema, schema_name);
63+
64+
// Delete the database and assert it's gone.
65+
databases_api::delete_database(config, &database_id)
66+
.await
67+
.expect("delete_database should succeed");
68+
69+
let after_delete = databases_api::get_database(config, &database_id).await;
70+
match after_delete {
71+
Err(err) => assert_eq!(
72+
common::status_of(&err),
73+
Some(404),
74+
"expected 404 after delete, got {err:?}"
75+
),
76+
Ok(_) => panic!("get_database should fail with 404 after delete"),
77+
}
78+
}

tests/embedding_providers_crud.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
//! Scenario: embedding_providers_crud.
2+
//!
3+
//! Defined in www.hotdata.dev/api/test-scenarios.yaml — register an embedding
4+
//! provider, read it, confirm it appears in list_embedding_providers, update it,
5+
//! then delete it.
6+
//!
7+
//! Uses a `service` provider with **no** api_key/secret_name (not `local` — the
8+
//! runtime currently rejects `local` as "not yet supported"). A service
9+
//! provider's key is only consulted when embeddings are actually generated, never
10+
//! at create/get/update, so this exercises the full CRUD surface without any real
11+
//! external credential and without auto-creating a secret that would need cleanup.
12+
13+
mod common;
14+
15+
use hotdata::apis::embedding_providers_api;
16+
use hotdata::models;
17+
18+
#[tokio::test]
19+
async fn embedding_providers_crud() {
20+
let client = skip_if_no_creds!();
21+
let config = client.configuration();
22+
23+
// Provider names follow the same underscore-normalized convention as secrets.
24+
let name = common::sdkci_name("embprov-crud").replace('-', "_");
25+
let renamed = format!("{name}_renamed");
26+
27+
let mut create_req =
28+
models::CreateEmbeddingProviderRequest::new(name.clone(), "service".to_string());
29+
create_req.config = Some(Some(
30+
serde_json::json!({ "model": "text-embedding-3-small" }),
31+
));
32+
let created = embedding_providers_api::create_embedding_provider(config, create_req)
33+
.await
34+
.expect("create_embedding_provider should succeed");
35+
assert_eq!(created.name, name);
36+
assert_eq!(created.provider_type, "service");
37+
assert!(!created.id.is_empty(), "created provider must have an id");
38+
39+
let fetched = embedding_providers_api::get_embedding_provider(config, &created.id)
40+
.await
41+
.expect("get_embedding_provider should succeed");
42+
assert_eq!(fetched.id, created.id);
43+
assert_eq!(fetched.name, name);
44+
assert_eq!(fetched.provider_type, "service");
45+
46+
let listing = embedding_providers_api::list_embedding_providers(config)
47+
.await
48+
.expect("list_embedding_providers should succeed");
49+
assert!(
50+
listing
51+
.embedding_providers
52+
.iter()
53+
.any(|p| p.id == created.id),
54+
"created provider {} not present in list_embedding_providers",
55+
created.id
56+
);
57+
58+
let mut update = models::UpdateEmbeddingProviderRequest::new();
59+
update.name = Some(Some(renamed.clone()));
60+
let updated = embedding_providers_api::update_embedding_provider(config, &created.id, update)
61+
.await
62+
.expect("update_embedding_provider should succeed");
63+
assert_eq!(updated.id, created.id);
64+
assert_eq!(updated.name, renamed);
65+
66+
embedding_providers_api::delete_embedding_provider(config, &created.id)
67+
.await
68+
.expect("delete_embedding_provider should succeed");
69+
70+
let after_delete = embedding_providers_api::get_embedding_provider(config, &created.id).await;
71+
match after_delete {
72+
Err(err) => assert_eq!(
73+
common::status_of(&err),
74+
Some(404),
75+
"expected 404 after delete, got {err:?}"
76+
),
77+
Ok(_) => panic!("get_embedding_provider should fail with 404 after delete"),
78+
}
79+
}
723 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)