// Oak Video Editor - Non-Linear Video Editor
// Copyright (C) 2026 Oak Team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//! Shared fixtures and helpers for the database-backend integration
//! tests (plan M13 D1/D3).
//!
//! Everything here is dialect-agnostic: the test bodies run against a
//! `oakdb+sqlite://…` or `oakdb+pg://…` uri string, and the helpers that
//! inspect rows behind the backend's back open a raw sea-orm connection
//! ([`inspect_sqlite`] / [`inspect_pg`]). `tests/database_test.rs` runs
//! the SQLite suite; `tests/database_pg_test.rs` runs the same behaviors
//! against a real PostgreSQL server gated on `OAK_TEST_PG_URL`.
// The module is compiled into two test binaries, each of which uses only
// the helpers for its own dialect, so the other dialect's helpers look
// dead to one binary while the other uses them.
#![allow(dead_code)]
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use sea_orm::entity::prelude::*;
use oakcore_rs::Rational;
use oaknode::block::ClipBlockBehavior;
use oaknode::footage::FootageBehavior;
use oaknode::id::NodeId;
use oaknode::keyframe::{Interpolation, Keyframe};
use oaknode::node::NodeCore;
use oaknode::project::Project;
use oaknode::sequence::SequenceBehavior;
use oaknode::track::{TrackBehavior, TrackListBehavior, TrackType};
use oaknode::value::NodeValue;
use oakstorage::backend::StorageBackend;
use oakstorage::backends::database::entities::settings;
use oakstorage::backends::database::DatabaseBackend;
use oakstorage::error::OAKSTORAGE_OK;
use oakstorage::handle::CHandle;
use oakstorage::nodeutil::{make_project_owned, project_arc};
use oakstorage::uri::StorageUri;
// ---------------------------------------------------------------------------
// URIs and paths
// ---------------------------------------------------------------------------
/// A fresh, unique temp directory for one test.
pub(crate) fn temp_dir(tag: &str) -> PathBuf {
let dir = std::env::temp_dir().join(format!("oakstorage_db_{}_{}", std::process::id(), tag));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
dir
}
/// `oakdb+sqlite:///…` URI for a database file.
pub(crate) fn db_uri(path: &Path) -> String {
format!("oakdb+sqlite://{}", path.display())
}
/// `oakdb+sqlite:///…?project=` URI selecting one library row.
pub(crate) fn project_uri(db: &str, uuid: &str) -> String {
format!("{db}?project={uuid}")
}
/// `file://…` URI for a plain file.
pub(crate) fn file_uri(path: &Path) -> String {
format!("file://{}", path.display())
}
// ---------------------------------------------------------------------------
// Save / load through the backend
// ---------------------------------------------------------------------------
/// Release an owned handle (refcount 1).
pub(crate) fn release(h: CHandle) {
if let Some(release) = h.release {
unsafe { release(h.ctx) };
}
}
/// Save an `Arc>` through the database backend.
pub(crate) fn save_project(
backend: &DatabaseBackend,
project: &Arc>,
uri: &str,
) -> oakstorage::error::Result<()> {
let parsed = StorageUri::parse(uri).unwrap();
let handle = make_project_owned(project.clone());
let result = backend.save(handle, &parsed, 0);
release(handle);
result
}
/// Load a project through a *new* database backend session (a fresh
/// connection pool = a fresh session), returning `(uuid, loaded)`.
pub(crate) fn load_project(backend: &DatabaseBackend, uri: &str) -> (String, Arc>) {
let parsed = StorageUri::parse(uri).unwrap();
let result = backend.load(&parsed).unwrap();
assert_eq!(result.version_info, OAKSTORAGE_OK);
let handle = result.project;
let loaded = unsafe { project_arc(&handle) }.unwrap();
let uuid = loaded.lock().unwrap().uuid.clone();
release(handle);
(uuid, loaded)
}
/// Load the state at `seq` (undo to any point).
pub(crate) fn load_at(backend: &DatabaseBackend, uri: &str, uuid: &str, seq: i64) -> Arc> {
let parsed = StorageUri::parse(uri).unwrap();
let handle = backend.load_at(&parsed, uuid, seq).unwrap();
let loaded = unsafe { project_arc(&handle) }.unwrap();
release(handle);
loaded
}
pub(crate) fn r_to_f(r: Rational) -> f64 {
r.numerator() as f64 / r.denominator() as f64
}
pub(crate) fn assert_close(a: f64, b: f64) {
assert!((a - b).abs() < 1e-6, "expected {a} close to {b}");
}
// ---------------------------------------------------------------------------
// Row inspection (raw sea-orm connections behind the backend's back)
// ---------------------------------------------------------------------------
/// Open a raw sea-orm SQLite connection to `path` and drive one future
/// against it on a private current-thread runtime.
pub(crate) fn inspect_sqlite(path: &Path, f: impl FnOnce(sea_orm::DatabaseConnection) -> Fut) -> R
where
Fut: std::future::Future