Land release blocker fixes and workspace cleanup
Some checks failed
check / cargo check (macos-latest) (push) Has been cancelled
check / cargo check (ubuntu-22.04) (push) Has been cancelled
check / cargo check (windows-latest) (push) Has been cancelled
check / svelte build + lint (push) Has been cancelled

This commit is contained in:
2026-04-23 00:16:09 +01:00
parent d7363cc913
commit 9b0067b4c0
36 changed files with 1529 additions and 418 deletions

View File

@@ -62,6 +62,13 @@ pub async fn insert_transcript(
pool: &SqlitePool,
params: &InsertTranscriptParams<'_>,
) -> Result<()> {
if !profile_exists(pool, params.profile_id).await? {
return Err(KonError::StorageError(format!(
"Insert transcript failed: unknown profile id '{}'",
params.profile_id
)));
}
sqlx::query(
"INSERT INTO transcripts (id, text, source, profile_id, title, audio_path, duration, engine, model_id, inference_ms, sample_rate, audio_channels, format_mode, remove_fillers, british_english, anti_hallucination)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
@@ -731,6 +738,12 @@ pub async fn delete_profile(pool: &SqlitePool, id: &str) -> Result<()> {
"Default profile cannot be deleted".into(),
));
}
let transcript_count = transcript_count_for_profile(pool, id).await?;
if transcript_count > 0 {
return Err(KonError::StorageError(format!(
"Cannot delete profile while {transcript_count} transcript(s) still reference it; reassign transcripts first"
)));
}
sqlx::query("DELETE FROM profiles WHERE id = ?")
.bind(id)
.execute(pool)
@@ -789,6 +802,23 @@ pub async fn delete_profile_term(pool: &SqlitePool, id: &str) -> Result<()> {
Ok(())
}
async fn profile_exists(pool: &SqlitePool, id: &str) -> Result<bool> {
let exists: Option<i64> = sqlx::query_scalar("SELECT 1 FROM profiles WHERE id = ? LIMIT 1")
.bind(id)
.fetch_optional(pool)
.await
.map_err(|e| KonError::StorageError(format!("Profile existence check failed: {e}")))?;
Ok(exists.is_some())
}
async fn transcript_count_for_profile(pool: &SqlitePool, id: &str) -> Result<i64> {
sqlx::query_scalar("SELECT COUNT(*) FROM transcripts WHERE profile_id = ?")
.bind(id)
.fetch_one(pool)
.await
.map_err(|e| KonError::StorageError(format!("Profile transcript count failed: {e}")))
}
// --- Error Logging ---
/// Log a structured error to the `error_log` table.
@@ -865,9 +895,14 @@ mod tests {
async fn test_pool() -> SqlitePool {
let pool = SqlitePoolOptions::new()
.max_connections(1)
.connect("sqlite::memory:")
.await
.unwrap();
sqlx::query("PRAGMA foreign_keys = ON")
.execute(&pool)
.await
.unwrap();
run_migrations(&pool).await.unwrap();
pool
}
@@ -1047,11 +1082,19 @@ mod tests {
insert_task(&pool, "p1", "Ship release", "inbox", None, None, None)
.await
.unwrap();
insert_subtask(&pool, "s1", "Final test", "p1").await.unwrap();
insert_subtask(&pool, "s2", "Tag release", "p1").await.unwrap();
insert_subtask(&pool, "s1", "Final test", "p1")
.await
.unwrap();
insert_subtask(&pool, "s2", "Tag release", "p1")
.await
.unwrap();
complete_subtask_and_check_parent(&pool, "s1").await.unwrap();
complete_subtask_and_check_parent(&pool, "s2").await.unwrap();
complete_subtask_and_check_parent(&pool, "s1")
.await
.unwrap();
complete_subtask_and_check_parent(&pool, "s2")
.await
.unwrap();
let parent = get_task_by_id(&pool, "p1").await.unwrap().unwrap();
assert!(parent.done);
@@ -1353,6 +1396,72 @@ mod tests {
assert_eq!(profiles.len(), 1);
}
#[tokio::test]
async fn insert_transcript_rejects_unknown_profile_id() {
let pool = test_pool().await;
let err = insert_transcript(
&pool,
&InsertTranscriptParams {
id: "bad-profile",
text: "Hello",
source: "microphone",
profile_id: "profile-missing",
title: None,
audio_path: None,
duration: 0.0,
engine: None,
model_id: None,
inference_ms: None,
sample_rate: None,
audio_channels: None,
format_mode: None,
remove_fillers: false,
british_english: false,
anti_hallucination: false,
},
)
.await
.expect_err("unknown profile id must be rejected");
let msg = err.to_string();
assert!(msg.contains("unknown profile id"), "got: {msg}");
}
#[tokio::test]
async fn delete_profile_rejects_when_transcripts_reference_it() {
let pool = test_pool().await;
let profile = create_profile(&pool, "Referenced", "").await.unwrap();
insert_transcript(
&pool,
&InsertTranscriptParams {
id: "referenced-transcript",
text: "Hello",
source: "microphone",
profile_id: &profile.id,
title: None,
audio_path: None,
duration: 0.0,
engine: None,
model_id: None,
inference_ms: None,
sample_rate: None,
audio_channels: None,
format_mode: None,
remove_fillers: false,
british_english: false,
anti_hallucination: false,
},
)
.await
.unwrap();
let err = delete_profile(&pool, &profile.id)
.await
.expect_err("profile with transcript references must not delete");
let msg = err.to_string();
assert!(msg.contains("reassign transcripts first"), "got: {msg}");
}
#[tokio::test]
async fn list_profile_terms_happy_path() {
let pool = test_pool().await;