Skip to content

Commit dedf4bd

Browse files
committed
post_threshold
1 parent d6c2b40 commit dedf4bd

File tree

1 file changed

+185
-2
lines changed
  • lib/bencher_schema/src/model/project/threshold

1 file changed

+185
-2
lines changed

lib/bencher_schema/src/model/project/threshold/mod.rs

Lines changed: 185 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ use bencher_json::{
99
};
1010
use diesel::{
1111
BelongingToDsl as _, Connection as _, ExpressionMethods as _, JoinOnDsl as _,
12-
NullableExpressionMethods as _, QueryDsl as _, RunQueryDsl as _, SelectableHelper as _,
12+
NullableExpressionMethods as _, OptionalExtension as _, QueryDsl as _, RunQueryDsl as _,
13+
SelectableHelper as _,
1314
};
1415
use dropshot::HttpError;
1516
use model::UpdateModel;
@@ -25,7 +26,10 @@ use super::{
2526
use crate::{
2627
auth_conn,
2728
context::{ApiContext, DbConnection},
28-
error::{BencherResource, assert_parentage, assert_siblings, resource_not_found_err},
29+
error::{
30+
BencherResource, assert_parentage, assert_siblings, resource_conflict_error,
31+
resource_not_found_err,
32+
},
2933
macros::{
3034
fn_get::{fn_get, fn_get_id, fn_get_uuid},
3135
sql::last_insert_rowid,
@@ -62,6 +66,20 @@ impl QueryThreshold {
6266
fn_get_id!(threshold, ThresholdId, ThresholdUuid);
6367
fn_get_uuid!(threshold, ThresholdId, ThresholdUuid);
6468

69+
pub fn find_by_dimensions(
70+
conn: &mut DbConnection,
71+
branch_id: BranchId,
72+
testbed_id: TestbedId,
73+
measure_id: MeasureId,
74+
) -> diesel::QueryResult<Option<Self>> {
75+
schema::threshold::table
76+
.filter(schema::threshold::branch_id.eq(branch_id))
77+
.filter(schema::threshold::testbed_id.eq(testbed_id))
78+
.filter(schema::threshold::measure_id.eq(measure_id))
79+
.first::<Self>(conn)
80+
.optional()
81+
}
82+
6583
pub fn get_with_uuid(
6684
conn: &mut DbConnection,
6785
query_project: &QueryProject,
@@ -352,6 +370,30 @@ impl InsertThreshold {
352370
measure_id: MeasureId,
353371
model: Model,
354372
) -> Result<ThresholdId, HttpError> {
373+
// Check for an existing threshold with the same unique key before writing.
374+
if let Some(existing) = QueryThreshold::find_by_dimensions(
375+
auth_conn!(context),
376+
branch_id,
377+
testbed_id,
378+
measure_id,
379+
)
380+
.map_err(|e| {
381+
crate::error::issue_error(
382+
"Failed to query threshold dimensions",
383+
"Failed to query threshold dimensions:",
384+
e,
385+
)
386+
})? {
387+
return Err(resource_conflict_error(
388+
BencherResource::Threshold,
389+
(branch_id, testbed_id, measure_id),
390+
format!(
391+
"A threshold ({}) already exists for this branch, testbed, and measure combination",
392+
existing.uuid
393+
),
394+
));
395+
}
396+
355397
#[cfg(feature = "plus")]
356398
Self::rate_limit(context, project_id).await?;
357399
let conn = write_conn!(context);
@@ -1917,4 +1959,145 @@ mod tests {
19171959
assert_eq!(results.len(), 20);
19181960
assert!(results.iter().all(|(_, m)| m.is_some()));
19191961
}
1962+
1963+
/// Test that `find_by_dimensions` returns `None` when no threshold exists.
1964+
#[test]
1965+
fn find_by_dimensions_returns_none_when_no_threshold() {
1966+
let mut conn = setup_test_db();
1967+
let base = create_base_entities(&mut conn);
1968+
1969+
let branch = create_branch_with_head(
1970+
&mut conn,
1971+
base.project_id,
1972+
"00000000-0000-0000-0000-000000000010",
1973+
"main",
1974+
"main",
1975+
"00000000-0000-0000-0000-000000000011",
1976+
);
1977+
1978+
let testbed = create_testbed(
1979+
&mut conn,
1980+
base.project_id,
1981+
"00000000-0000-0000-0000-000000000020",
1982+
"localhost",
1983+
"localhost",
1984+
);
1985+
1986+
let measure = create_measure(
1987+
&mut conn,
1988+
base.project_id,
1989+
"00000000-0000-0000-0000-000000000030",
1990+
"latency",
1991+
"latency",
1992+
);
1993+
1994+
let result =
1995+
QueryThreshold::find_by_dimensions(&mut conn, branch.branch_id, testbed, measure)
1996+
.expect("Query should succeed");
1997+
assert!(result.is_none());
1998+
}
1999+
2000+
/// Test that `find_by_dimensions` returns the existing threshold.
2001+
#[test]
2002+
fn find_by_dimensions_returns_existing_threshold() {
2003+
let mut conn = setup_test_db();
2004+
let base = create_base_entities(&mut conn);
2005+
2006+
let branch = create_branch_with_head(
2007+
&mut conn,
2008+
base.project_id,
2009+
"00000000-0000-0000-0000-000000000010",
2010+
"main",
2011+
"main",
2012+
"00000000-0000-0000-0000-000000000011",
2013+
);
2014+
2015+
let testbed = create_testbed(
2016+
&mut conn,
2017+
base.project_id,
2018+
"00000000-0000-0000-0000-000000000020",
2019+
"localhost",
2020+
"localhost",
2021+
);
2022+
2023+
let measure = create_measure(
2024+
&mut conn,
2025+
base.project_id,
2026+
"00000000-0000-0000-0000-000000000030",
2027+
"latency",
2028+
"latency",
2029+
);
2030+
2031+
let threshold_id = create_threshold(
2032+
&mut conn,
2033+
base.project_id,
2034+
branch.branch_id,
2035+
testbed,
2036+
measure,
2037+
"00000000-0000-0000-0000-000000000040",
2038+
);
2039+
2040+
let result =
2041+
QueryThreshold::find_by_dimensions(&mut conn, branch.branch_id, testbed, measure)
2042+
.expect("Query should succeed");
2043+
assert!(result.is_some());
2044+
assert_eq!(result.unwrap().id, threshold_id);
2045+
}
2046+
2047+
/// Test that inserting a duplicate threshold violates the UNIQUE constraint.
2048+
#[test]
2049+
fn duplicate_threshold_insert_fails_with_unique_constraint() {
2050+
let mut conn = setup_test_db();
2051+
let base = create_base_entities(&mut conn);
2052+
2053+
let branch = create_branch_with_head(
2054+
&mut conn,
2055+
base.project_id,
2056+
"00000000-0000-0000-0000-000000000010",
2057+
"main",
2058+
"main",
2059+
"00000000-0000-0000-0000-000000000011",
2060+
);
2061+
2062+
let testbed = create_testbed(
2063+
&mut conn,
2064+
base.project_id,
2065+
"00000000-0000-0000-0000-000000000020",
2066+
"localhost",
2067+
"localhost",
2068+
);
2069+
2070+
let measure = create_measure(
2071+
&mut conn,
2072+
base.project_id,
2073+
"00000000-0000-0000-0000-000000000030",
2074+
"latency",
2075+
"latency",
2076+
);
2077+
2078+
// First insert succeeds
2079+
create_threshold(
2080+
&mut conn,
2081+
base.project_id,
2082+
branch.branch_id,
2083+
testbed,
2084+
measure,
2085+
"00000000-0000-0000-0000-000000000040",
2086+
);
2087+
2088+
// Second insert with same (branch, testbed, measure) should fail
2089+
let result = diesel::insert_into(schema::threshold::table)
2090+
.values((
2091+
schema::threshold::uuid.eq("00000000-0000-0000-0000-000000000041"),
2092+
schema::threshold::project_id.eq(base.project_id),
2093+
schema::threshold::branch_id.eq(branch.branch_id),
2094+
schema::threshold::testbed_id.eq(testbed),
2095+
schema::threshold::measure_id.eq(measure),
2096+
schema::threshold::created.eq(bencher_json::DateTime::TEST),
2097+
schema::threshold::modified.eq(bencher_json::DateTime::TEST),
2098+
))
2099+
.execute(&mut conn);
2100+
2101+
assert!(result.is_err());
2102+
}
19202103
}

0 commit comments

Comments
 (0)