use std::sync::Arc;
use std::time::Duration;
use sqlx::SqlitePool;
use time::OffsetDateTime;
use uuid::Uuid;
use super::planificacion::{Ajustes as AjustesDelPlanificador, Planificador};
use crate::db::entities::{
AltaDeEjecucion, AltaDeRepositorio, AltaDeTarea, AltaDeTarget, Ejecucion, Ejecuciones, Estado,
Repositorios, Restauraciones, Tareas, Targets, TipoDeTarget,
};
use crate::engine::{Ajustes as AjustesDelMotor, Dependencias, Motor};
use crate::rustic::{Acceso, BarrasDelMotor, Error, PuestaEnMarcha, Repositorio};
use crate::secret::SecretString;
use crate::test_support::{
CLAVE_SECRETA_DE_PRUEBA, DirectorioDePrueba, PASSPHRASE_DE_PRUEBA, base_con_clave,
directorio_de_prueba,
};
pub(crate) const SOCKET_QUE_NO_EXISTE: &str = "unix:///no/existe/kesto-planificador.sock";
pub(crate) const CRON_DE_CINCO: &str = "0 3 * * *";
const LO_QUE_TARDA_EN_NO_ABRIR: Duration = Duration::from_secs(1);
fn abridor_que_no_abre(
acceso: &Acceso,
_barras: BarrasDelMotor,
) -> Result<(Repositorio, PuestaEnMarcha), Error> {
std::thread::sleep(LO_QUE_TARDA_EN_NO_ABRIR);
Err(Error::DestinoNoAlcanzable {
destino: acceso.endpoint.clone(),
motivo: "el abridor de esta prueba no abre nada".to_owned(),
})
}
pub(crate) struct Aparato {
_base: DirectorioDePrueba,
pub(crate) pool: SqlitePool,
pub(crate) motor: Motor,
pub(crate) tareas: Tareas,
pub(crate) ejecuciones: Ejecuciones,
pub(crate) repositorio: Uuid,
pub(crate) target: Uuid,
_destino: DirectorioDePrueba,
}
impl Aparato {
pub(crate) async fn nuevo() -> Self {
let (base, pool, clave) = base_con_clave().await;
let destino_guardado = directorio_de_prueba();
let destino = destino_guardado.ruta().to_string_lossy().into_owned();
let repositorios = Repositorios::nuevo(pool.clone(), clave.clone());
let targets = Targets::nuevo(pool.clone(), clave.clone());
let repositorio = repositorios
.alta(AltaDeRepositorio {
name: "repositorio-del-planificador".to_owned(),
endpoint: destino.clone(),
bucket: "cubo-de-prueba".to_owned(),
region: "region-de-prueba".to_owned(),
access_key_id: "clave-de-acceso-de-prueba".to_owned(),
secret_access_key: SecretString::new(CLAVE_SECRETA_DE_PRUEBA.to_owned()),
passphrase: SecretString::new(PASSPHRASE_DE_PRUEBA.to_owned()),
})
.await
.expect("el alta del repositorio no puede fallar")
.id;
let target = targets
.alta(AltaDeTarget {
name: "target-del-planificador".to_owned(),
target_type: TipoDeTarget::Directory,
container_name: None,
db_user: None,
db_password: None,
db_name: None,
host_path: Some(destino),
db_path: None,
})
.await
.expect("el alta del target no puede fallar")
.id;
let motor = Motor::arrancar(
AjustesDelMotor::nuevos(SOCKET_QUE_NO_EXISTE).con_abridor(abridor_que_no_abre),
Dependencias {
repositorios: Arc::new(repositorios),
targets: Arc::new(targets),
tareas: Arc::new(Tareas::nuevo(pool.clone())),
ejecuciones: Arc::new(Ejecuciones::nuevo(pool.clone(), clave.clone())),
restauraciones: Arc::new(Restauraciones::nuevo(pool.clone())),
},
)
.expect("el motor se monta desde dentro del runtime de la prueba");
Self {
_base: base,
pool: pool.clone(),
motor,
tareas: Tareas::nuevo(pool.clone()),
ejecuciones: Ejecuciones::nuevo(pool, clave),
repositorio,
target,
_destino: destino_guardado,
}
}
pub(crate) async fn planificador(&self) -> Planificador {
Planificador::arrancar(
self.motor.clone(),
self.pool.clone(),
AjustesDelPlanificador::nuevos(),
)
.await
.expect("el arranque del planificador no puede fallar en una prueba")
}
pub(crate) async fn tarea(&self, cron: &str, enabled: bool) -> Uuid {
self.tareas
.alta(AltaDeTarea {
target_id: self.target,
repository_id: self.repositorio,
cron_expression: cron.to_owned(),
keep_daily: 7,
keep_weekly: 4,
keep_monthly: 12,
enabled,
name: None,
})
.await
.expect("el alta de la tarea no puede fallar")
.id
}
pub(crate) async fn cambiar_enabled(&self, tarea: &Uuid, enabled: bool) {
let mut fila = self
.tareas
.leer(tarea)
.await
.expect("la lectura de la tarea no puede fallar")
.expect("la tarea de la prueba tiene que estar");
fila.enabled = enabled;
assert!(
self.tareas
.actualizar(&fila)
.await
.expect("la edición de la tarea no puede fallar"),
"el cambio de `enabled` tenía que quedar escrito"
);
}
pub(crate) async fn cambiar_cron(&self, tarea: &Uuid, cron: &str) {
let mut fila = self
.tareas
.leer(tarea)
.await
.expect("la lectura de la tarea no puede fallar")
.expect("la tarea de la prueba tiene que estar");
fila.cron_expression = cron.to_owned();
assert!(
self.tareas
.actualizar(&fila)
.await
.expect("la edición de la tarea no puede fallar"),
"el cambio de expresión tenía que quedar escrito"
);
}
pub(crate) async fn dejar_una_en_curso(&self, tarea: &Uuid) {
self.ejecuciones
.alta(AltaDeEjecucion {
job_id: *tarea,
status: Estado::Pending,
snapshot_id: None,
bytes_processed: 0,
bytes_uploaded: 0,
error_message: None,
started_at: OffsetDateTime::now_utc(),
finished_at: None,
})
.await
.expect("el alta de la ejecución de prueba no puede fallar");
}
pub(crate) async fn ejecuciones_de(&self, tarea: &Uuid) -> i64 {
sqlx::query_scalar("SELECT COUNT(*) FROM job_executions WHERE job_id = ?1")
.bind(tarea.hyphenated().to_string())
.fetch_one(&self.pool)
.await
.expect("el recuento de ejecuciones debe poder leerse")
}
pub(crate) async fn estado_de_la_ejecucion(&self, tarea: &Uuid) -> String {
sqlx::query_scalar("SELECT status FROM job_executions WHERE job_id = ?1")
.bind(tarea.hyphenated().to_string())
.fetch_one(&self.pool)
.await
.expect("la fila de la ejecución tiene que existir")
}
pub(crate) async fn ejecucion_de(&self, tarea: &Uuid) -> Ejecucion {
let id: String = sqlx::query_scalar("SELECT id FROM job_executions WHERE job_id = ?1")
.bind(tarea.hyphenated().to_string())
.fetch_one(&self.pool)
.await
.expect("la fila de la ejecución tiene que existir");
let id = Uuid::parse_str(&id).expect("el identificador guardado es un UUID con guiones");
self.ejecuciones
.leer(&id)
.await
.expect("la lectura de la ejecución no puede fallar")
.expect("la ejecución que se acaba de localizar tiene que seguir ahí")
}
pub(crate) async fn columnas_de_ejecuciones(&self) -> Vec<String> {
sqlx::query_scalar("SELECT name FROM pragma_table_info('job_executions')")
.fetch_all(&self.pool)
.await
.expect("las columnas de job_executions deben poder leerse")
}
}