test(saas): Phase 1 integration tests — billing + scheduled_task + knowledge (68 tests)
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
- Fix TIMESTAMPTZ decode errors: add ::TEXT cast to all SELECT queries
where Row structs use String for TIMESTAMPTZ columns (~22 locations)
- Fix Axum 0.7 route params: {id} → :id in billing/knowledge/scheduled_task routes
- Fix JSONB bind: scheduled_task INSERT uses ::jsonb cast for input_payload
- Add billing_test.rs (14 tests): plans, subscription, usage, payments, invoices
- Add scheduled_task_test.rs (12 tests): CRUD, validation, isolation
- Add knowledge_test.rs (20 tests): categories, items, versions, search, analytics, permissions
- Fix auth test regression: 6 tests were failing due to TIMESTAMPTZ type mismatch
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -15,19 +15,19 @@ pub(crate) async fn fetch_all_config_items(
|
||||
) -> SaasResult<Vec<ConfigItemInfo>> {
|
||||
let sql = match (&query.category, &query.source) {
|
||||
(Some(_), Some(_)) => {
|
||||
"SELECT id, category, key_path, value_type, current_value, default_value, source, description, requires_restart, created_at, updated_at
|
||||
"SELECT id, category, key_path, value_type, current_value, default_value, source, description, requires_restart, created_at::TEXT, updated_at::TEXT
|
||||
FROM config_items WHERE category = $1 AND source = $2 ORDER BY category, key_path"
|
||||
}
|
||||
(Some(_), None) => {
|
||||
"SELECT id, category, key_path, value_type, current_value, default_value, source, description, requires_restart, created_at, updated_at
|
||||
"SELECT id, category, key_path, value_type, current_value, default_value, source, description, requires_restart, created_at::TEXT, updated_at::TEXT
|
||||
FROM config_items WHERE category = $1 ORDER BY key_path"
|
||||
}
|
||||
(None, Some(_)) => {
|
||||
"SELECT id, category, key_path, value_type, current_value, default_value, source, description, requires_restart, created_at, updated_at
|
||||
"SELECT id, category, key_path, value_type, current_value, default_value, source, description, requires_restart, created_at::TEXT, updated_at::TEXT
|
||||
FROM config_items WHERE source = $1 ORDER BY category, key_path"
|
||||
}
|
||||
(None, None) => {
|
||||
"SELECT id, category, key_path, value_type, current_value, default_value, source, description, requires_restart, created_at, updated_at
|
||||
"SELECT id, category, key_path, value_type, current_value, default_value, source, description, requires_restart, created_at::TEXT, updated_at::TEXT
|
||||
FROM config_items ORDER BY category, key_path"
|
||||
}
|
||||
};
|
||||
@@ -61,7 +61,7 @@ pub async fn list_config_items(
|
||||
"SELECT COUNT(*) FROM config_items WHERE category = $1 AND source = $2"
|
||||
).bind(cat).bind(src).fetch_one(db).await?;
|
||||
let rows = sqlx::query_as::<_, ConfigItemRow>(
|
||||
"SELECT id, category, key_path, value_type, current_value, default_value, source, description, requires_restart, created_at, updated_at
|
||||
"SELECT id, category, key_path, value_type, current_value, default_value, source, description, requires_restart, created_at::TEXT, updated_at::TEXT
|
||||
FROM config_items WHERE category = $1 AND source = $2 ORDER BY category, key_path LIMIT $3 OFFSET $4"
|
||||
).bind(cat).bind(src).bind(ps as i64).bind(offset).fetch_all(db).await?;
|
||||
(total, rows)
|
||||
@@ -71,7 +71,7 @@ pub async fn list_config_items(
|
||||
"SELECT COUNT(*) FROM config_items WHERE category = $1"
|
||||
).bind(cat).fetch_one(db).await?;
|
||||
let rows = sqlx::query_as::<_, ConfigItemRow>(
|
||||
"SELECT id, category, key_path, value_type, current_value, default_value, source, description, requires_restart, created_at, updated_at
|
||||
"SELECT id, category, key_path, value_type, current_value, default_value, source, description, requires_restart, created_at::TEXT, updated_at::TEXT
|
||||
FROM config_items WHERE category = $1 ORDER BY category, key_path LIMIT $2 OFFSET $3"
|
||||
).bind(cat).bind(ps as i64).bind(offset).fetch_all(db).await?;
|
||||
(total, rows)
|
||||
@@ -81,7 +81,7 @@ pub async fn list_config_items(
|
||||
"SELECT COUNT(*) FROM config_items WHERE source = $1"
|
||||
).bind(src).fetch_one(db).await?;
|
||||
let rows = sqlx::query_as::<_, ConfigItemRow>(
|
||||
"SELECT id, category, key_path, value_type, current_value, default_value, source, description, requires_restart, created_at, updated_at
|
||||
"SELECT id, category, key_path, value_type, current_value, default_value, source, description, requires_restart, created_at::TEXT, updated_at::TEXT
|
||||
FROM config_items WHERE source = $1 ORDER BY category, key_path LIMIT $2 OFFSET $3"
|
||||
).bind(src).bind(ps as i64).bind(offset).fetch_all(db).await?;
|
||||
(total, rows)
|
||||
@@ -91,7 +91,7 @@ pub async fn list_config_items(
|
||||
"SELECT COUNT(*) FROM config_items"
|
||||
).fetch_one(db).await?;
|
||||
let rows = sqlx::query_as::<_, ConfigItemRow>(
|
||||
"SELECT id, category, key_path, value_type, current_value, default_value, source, description, requires_restart, created_at, updated_at
|
||||
"SELECT id, category, key_path, value_type, current_value, default_value, source, description, requires_restart, created_at::TEXT, updated_at::TEXT
|
||||
FROM config_items ORDER BY category, key_path LIMIT $1 OFFSET $2"
|
||||
).bind(ps as i64).bind(offset).fetch_all(db).await?;
|
||||
(total, rows)
|
||||
@@ -108,7 +108,7 @@ pub async fn list_config_items(
|
||||
pub async fn get_config_item(db: &PgPool, item_id: &str) -> SaasResult<ConfigItemInfo> {
|
||||
let row: Option<ConfigItemRow> =
|
||||
sqlx::query_as(
|
||||
"SELECT id, category, key_path, value_type, current_value, default_value, source, description, requires_restart, created_at, updated_at
|
||||
"SELECT id, category, key_path, value_type, current_value, default_value, source, description, requires_restart, created_at::TEXT, updated_at::TEXT
|
||||
FROM config_items WHERE id = $1"
|
||||
)
|
||||
.bind(item_id)
|
||||
@@ -124,7 +124,7 @@ pub async fn create_config_item(
|
||||
db: &PgPool, req: &CreateConfigItemRequest,
|
||||
) -> SaasResult<ConfigItemInfo> {
|
||||
let id = uuid::Uuid::new_v4().to_string();
|
||||
let now = chrono::Utc::now().to_rfc3339();
|
||||
let now = chrono::Utc::now();
|
||||
let source = req.source.as_deref().unwrap_or("local");
|
||||
let requires_restart = req.requires_restart.unwrap_or(false);
|
||||
|
||||
@@ -156,7 +156,7 @@ pub async fn create_config_item(
|
||||
pub async fn update_config_item(
|
||||
db: &PgPool, item_id: &str, req: &UpdateConfigItemRequest,
|
||||
) -> SaasResult<ConfigItemInfo> {
|
||||
let now = chrono::Utc::now().to_rfc3339();
|
||||
let now = chrono::Utc::now();
|
||||
|
||||
// COALESCE pattern: all updatable fields in a single static SQL.
|
||||
// NULL parameters leave the column unchanged.
|
||||
@@ -244,7 +244,7 @@ pub async fn seed_default_config_items(db: &PgPool) -> SaasResult<usize> {
|
||||
];
|
||||
|
||||
let mut created = 0;
|
||||
let now = chrono::Utc::now().to_rfc3339();
|
||||
let now = chrono::Utc::now();
|
||||
|
||||
for (category, key_path, value_type, default_value, current_value, description) in defaults {
|
||||
let existing: Option<(String,)> = sqlx::query_as(
|
||||
@@ -319,7 +319,7 @@ pub async fn compute_config_diff(
|
||||
pub async fn sync_config(
|
||||
db: &PgPool, account_id: &str, req: &SyncConfigRequest,
|
||||
) -> SaasResult<ConfigSyncResult> {
|
||||
let now = chrono::Utc::now().to_rfc3339();
|
||||
let now = chrono::Utc::now();
|
||||
let config_keys_str = serde_json::to_string(&req.config_keys)?;
|
||||
let client_values_str = Some(serde_json::to_string(&req.client_values)?);
|
||||
|
||||
@@ -458,7 +458,7 @@ pub async fn list_sync_logs(
|
||||
|
||||
let rows: Vec<ConfigSyncLogRow> =
|
||||
sqlx::query_as(
|
||||
"SELECT id, account_id, client_fingerprint, action, config_keys, client_values, saas_values, resolution, created_at
|
||||
"SELECT id, account_id, client_fingerprint, action, config_keys, client_values, saas_values, resolution, created_at::TEXT
|
||||
FROM config_sync_log WHERE account_id = $1 ORDER BY created_at DESC LIMIT $2 OFFSET $3"
|
||||
)
|
||||
.bind(account_id)
|
||||
|
||||
Reference in New Issue
Block a user