This commit is contained in:
parent
2e39ef952b
commit
79c4ab6c2b
36
Cargo.lock
generated
36
Cargo.lock
generated
|
@ -794,6 +794,16 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_logger"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_logger"
|
name = "env_logger"
|
||||||
version = "0.11.3"
|
version = "0.11.3"
|
||||||
|
@ -1219,7 +1229,7 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"datamatrix",
|
"datamatrix",
|
||||||
"enum-iterator",
|
"enum-iterator",
|
||||||
"env_logger",
|
"env_logger 0.11.3",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"itertools",
|
"itertools",
|
||||||
"log",
|
"log",
|
||||||
|
@ -1227,6 +1237,8 @@ dependencies = [
|
||||||
"mime",
|
"mime",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
"printpdf",
|
"printpdf",
|
||||||
|
"quickcheck",
|
||||||
|
"quickcheck_macros",
|
||||||
"rust-embed",
|
"rust-embed",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
|
@ -1664,6 +1676,28 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quickcheck"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6"
|
||||||
|
dependencies = [
|
||||||
|
"env_logger 0.8.4",
|
||||||
|
"log",
|
||||||
|
"rand",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quickcheck_macros"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.36"
|
version = "1.0.36"
|
||||||
|
|
|
@ -35,5 +35,9 @@ thiserror = "1.0.61"
|
||||||
time = { version = "0.3.36", features = ["parsing", "serde"] }
|
time = { version = "0.3.36", features = ["parsing", "serde"] }
|
||||||
uuid = { version = "1.9.0", features = ["serde", "v4"] }
|
uuid = { version = "1.9.0", features = ["serde", "v4"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
quickcheck = "1.0.3"
|
||||||
|
quickcheck_macros = "1.0.0"
|
||||||
|
|
||||||
[profile.dev.package.sqlx-macros]
|
[profile.dev.package.sqlx-macros]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|
|
@ -2,15 +2,20 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
use enum_iterator::Sequence;
|
||||||
use maud::{html, Markup, Render};
|
use maud::{html, Markup, Render};
|
||||||
|
#[cfg(test)]
|
||||||
|
use quickcheck::{Arbitrary, Gen};
|
||||||
use sqlx::{query, PgPool};
|
use sqlx::{query, PgPool};
|
||||||
|
|
||||||
use crate::frontend::templates::helpers::Colour;
|
use crate::frontend::templates::helpers::Colour;
|
||||||
|
|
||||||
use super::item_events::ItemEvent;
|
use super::item_events::ItemEvent;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, sqlx::Type)]
|
#[derive(Clone, Copy, Debug, PartialEq, sqlx::Type)]
|
||||||
#[sqlx(rename_all = "snake_case", type_name = "item_state")]
|
#[sqlx(rename_all = "snake_case", type_name = "item_state")]
|
||||||
|
#[cfg_attr(test, derive(Sequence))]
|
||||||
pub enum ItemState {
|
pub enum ItemState {
|
||||||
Borrowed,
|
Borrowed,
|
||||||
Inactive,
|
Inactive,
|
||||||
|
@ -18,6 +23,15 @@ pub enum ItemState {
|
||||||
Owned,
|
Owned,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
impl Arbitrary for ItemState {
|
||||||
|
fn arbitrary(g: &mut Gen) -> Self {
|
||||||
|
enum_iterator::all::<Self>()
|
||||||
|
.nth(usize::arbitrary(g) % enum_iterator::cardinality::<Self>())
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ItemState {
|
impl ItemState {
|
||||||
pub fn colour(&self) -> Colour {
|
pub fn colour(&self) -> Colour {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
@ -65,3 +65,132 @@ impl ItemRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use uuid::{uuid, Uuid};
|
||||||
|
|
||||||
|
use crate::database::{items::ItemAddForm, ItemRepository};
|
||||||
|
use crate::test_utils::*;
|
||||||
|
|
||||||
|
#[sqlx::test(fixtures(path = "../../../tests/fixtures", scripts("default")))]
|
||||||
|
async fn simple_success(pool: sqlx::PgPool) -> sqlx::Result<()> {
|
||||||
|
let repo = ItemRepository::new(pool.clone());
|
||||||
|
|
||||||
|
let added_item = repo
|
||||||
|
.add(ItemAddForm {
|
||||||
|
quantity: 1,
|
||||||
|
name: None,
|
||||||
|
parent: None,
|
||||||
|
class: uuid!("e993e21c-8558-49e7-a993-2a6a61c1d55c"),
|
||||||
|
original_packaging: None,
|
||||||
|
description: "descr".to_string(),
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
assert_eq!(added_item.len(), 1);
|
||||||
|
|
||||||
|
let added_item = added_item.first();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
sqlx::query_scalar(
|
||||||
|
"SELECT
|
||||||
|
items.name IS NULL
|
||||||
|
AND items.parent IS NULL
|
||||||
|
AND items.class = 'e993e21c-8558-49e7-a993-2a6a61c1d55c'
|
||||||
|
AND items.original_packaging IS NULL
|
||||||
|
AND items.description = 'descr'
|
||||||
|
FROM items
|
||||||
|
WHERE items.id = $1"
|
||||||
|
)
|
||||||
|
.bind(added_item)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await?
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sqlx::test(fixtures(path = "../../../tests/fixtures", scripts("default")))]
|
||||||
|
async fn complex_success(pool: sqlx::PgPool) -> sqlx::Result<()> {
|
||||||
|
let repo = ItemRepository::new(pool.clone());
|
||||||
|
|
||||||
|
let added_items = repo
|
||||||
|
.add(ItemAddForm {
|
||||||
|
quantity: 7,
|
||||||
|
name: Some("Yeeeet".to_string()),
|
||||||
|
parent: Some(uuid!("4fc0f5f4-4dca-4c24-844d-1f464cb32afa")),
|
||||||
|
class: uuid!("e993e21c-8558-49e7-a993-2a6a61c1d55c"),
|
||||||
|
original_packaging: Some(uuid!("554b11ce-fecb-4020-981e-acabbf7b5913")),
|
||||||
|
description: "Lorem ipsum.".to_string(),
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
assert_eq!(added_items.len(), 7);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
sqlx::query_scalar(
|
||||||
|
"SELECT
|
||||||
|
items.name = 'Yeeeet'
|
||||||
|
AND items.parent = '4fc0f5f4-4dca-4c24-844d-1f464cb32afa'
|
||||||
|
AND items.class = 'e993e21c-8558-49e7-a993-2a6a61c1d55c'
|
||||||
|
AND items.original_packaging = '554b11ce-fecb-4020-981e-acabbf7b5913'
|
||||||
|
AND items.description = 'Lorem ipsum.'
|
||||||
|
FROM items
|
||||||
|
WHERE items.id = ANY ($1)"
|
||||||
|
)
|
||||||
|
.bind(added_items)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await?
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sqlx::test(fixtures(path = "../../../tests/fixtures", scripts("default")))]
|
||||||
|
async fn invalid_references(pool: sqlx::PgPool) -> sqlx::Result<()> {
|
||||||
|
let repo = ItemRepository::new(pool.clone());
|
||||||
|
|
||||||
|
let item_count_before = item_count(&pool).await?;
|
||||||
|
|
||||||
|
assert!(repo
|
||||||
|
.add(ItemAddForm {
|
||||||
|
quantity: 1,
|
||||||
|
name: None,
|
||||||
|
parent: Some(Uuid::new_v4()),
|
||||||
|
class: uuid!("e993e21c-8558-49e7-a993-2a6a61c1d55c"),
|
||||||
|
original_packaging: None,
|
||||||
|
description: "".to_string(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.is_err());
|
||||||
|
|
||||||
|
assert!(repo
|
||||||
|
.add(ItemAddForm {
|
||||||
|
quantity: 1,
|
||||||
|
name: None,
|
||||||
|
parent: None,
|
||||||
|
class: Uuid::new_v4(),
|
||||||
|
original_packaging: None,
|
||||||
|
description: "".to_string(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.is_err());
|
||||||
|
|
||||||
|
assert!(repo
|
||||||
|
.add(ItemAddForm {
|
||||||
|
quantity: 1,
|
||||||
|
name: None,
|
||||||
|
parent: None,
|
||||||
|
class: uuid!("e993e21c-8558-49e7-a993-2a6a61c1d55c"),
|
||||||
|
original_packaging: Some(Uuid::new_v4()),
|
||||||
|
description: "".to_string(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.is_err());
|
||||||
|
|
||||||
|
assert_eq!(item_count(&pool).await?, item_count_before);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -29,3 +29,35 @@ impl ItemRepository {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use maud::Render;
|
||||||
|
|
||||||
|
use crate::database::{items::ItemName, ItemRepository};
|
||||||
|
use crate::test_utils::*;
|
||||||
|
|
||||||
|
#[sqlx::test(fixtures(path = "../../../tests/fixtures", scripts("default")))]
|
||||||
|
async fn datalist(pool: sqlx::PgPool) -> sqlx::Result<()> {
|
||||||
|
let repo = ItemRepository::new(pool.clone());
|
||||||
|
|
||||||
|
let dl = repo.datalist().await?;
|
||||||
|
|
||||||
|
assert_eq!(dl.name, "items".to_string());
|
||||||
|
assert_eq!(dl.link_prefix, Some("/item/".to_string()));
|
||||||
|
assert_eq!(dl.options.len(), item_count(&pool).await? as usize);
|
||||||
|
|
||||||
|
// can’t compare Box<dyn Render>
|
||||||
|
let option = dl
|
||||||
|
.options
|
||||||
|
.iter()
|
||||||
|
.find(|option| option.value == "554b11ce-fecb-4020-981e-acabbf7b5913")
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
option.text.render().into_string(),
|
||||||
|
ItemName::Item("Item 4".to_string()).render().into_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -16,3 +16,48 @@ impl ItemRepository {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use uuid::{uuid, Uuid};
|
||||||
|
|
||||||
|
use crate::database::ItemRepository;
|
||||||
|
use crate::test_utils::*;
|
||||||
|
|
||||||
|
#[sqlx::test(fixtures(path = "../../../tests/fixtures", scripts("default")))]
|
||||||
|
async fn success(pool: sqlx::PgPool) -> sqlx::Result<()> {
|
||||||
|
let item_count_before = item_count(&pool).await?;
|
||||||
|
|
||||||
|
let repo = ItemRepository::new(pool.clone());
|
||||||
|
repo.delete(uuid!("663f45e6-b11a-4197-8ce4-c784ac9ee617"))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
assert_eq!(item_count(&pool).await?, item_count_before - 1);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sqlx::test(fixtures(path = "../../../tests/fixtures", scripts("default")))]
|
||||||
|
async fn prevented_by_constraint(pool: sqlx::PgPool) -> sqlx::Result<()> {
|
||||||
|
let repo = ItemRepository::new(pool.clone());
|
||||||
|
|
||||||
|
assert!(repo
|
||||||
|
.delete(uuid!("3003e61f-0824-4625-9b72-eeb9f11a6a26"))
|
||||||
|
.await
|
||||||
|
.is_err());
|
||||||
|
|
||||||
|
repo.delete(uuid!("554b11ce-fecb-4020-981e-acabbf7b5913"))
|
||||||
|
.await?;
|
||||||
|
repo.delete(uuid!("3003e61f-0824-4625-9b72-eeb9f11a6a26"))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sqlx::test]
|
||||||
|
async fn invalid_id(pool: sqlx::PgPool) {
|
||||||
|
let repo = ItemRepository::new(pool.clone());
|
||||||
|
|
||||||
|
assert!(repo.delete(Uuid::new_v4()).await.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -59,3 +59,89 @@ impl ItemRepository {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use uuid::uuid;
|
||||||
|
|
||||||
|
use crate::database::{items::ItemEditForm, ItemRepository};
|
||||||
|
|
||||||
|
#[sqlx::test(fixtures(path = "../../../tests/fixtures", scripts("default")))]
|
||||||
|
async fn prefill_form(pool: sqlx::PgPool) -> sqlx::Result<()> {
|
||||||
|
let repo = ItemRepository::new(pool.clone());
|
||||||
|
|
||||||
|
let form = repo
|
||||||
|
.edit_form(uuid!("663f45e6-b11a-4197-8ce4-c784ac9ee617"))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
assert_eq!(form.name, Some("Item 2".to_string()));
|
||||||
|
assert_eq!(
|
||||||
|
form.parent,
|
||||||
|
Some(uuid!("4fc0f5f4-4dca-4c24-844d-1f464cb32afa"))
|
||||||
|
);
|
||||||
|
assert_eq!(form.class, uuid!("8a979306-b4c6-4ef8-900d-68f64abb2975"));
|
||||||
|
assert_eq!(
|
||||||
|
form.original_packaging,
|
||||||
|
Some(uuid!("049298e2-73db-42fb-957d-a741655648b1"))
|
||||||
|
);
|
||||||
|
assert_eq!(form.description, "Lorem ipsum 3".to_string());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sqlx::test(fixtures(path = "../../../tests/fixtures", scripts("default")))]
|
||||||
|
async fn edit(pool: sqlx::PgPool) -> sqlx::Result<()> {
|
||||||
|
let repo = ItemRepository::new(pool.clone());
|
||||||
|
|
||||||
|
repo.edit(
|
||||||
|
uuid!("554b11ce-fecb-4020-981e-acabbf7b5913"),
|
||||||
|
&ItemEditForm {
|
||||||
|
name: Some("Totally new name".to_string()),
|
||||||
|
parent: Some(uuid!("4fc0f5f4-4dca-4c24-844d-1f464cb32afa")),
|
||||||
|
class: uuid!("04527cc8-2fbf-4a99-aa0a-361252c8f6d3"),
|
||||||
|
original_packaging: Some(uuid!("3003e61f-0824-4625-9b72-eeb9f11a6a26")),
|
||||||
|
description: "never seen before".to_string(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
sqlx::query_scalar(
|
||||||
|
"SELECT
|
||||||
|
items.name = 'Totally new name'
|
||||||
|
AND items.parent = '4fc0f5f4-4dca-4c24-844d-1f464cb32afa'
|
||||||
|
AND items.class = '04527cc8-2fbf-4a99-aa0a-361252c8f6d3'
|
||||||
|
AND items.original_packaging = '3003e61f-0824-4625-9b72-eeb9f11a6a26'
|
||||||
|
AND items.description = 'never seen before'
|
||||||
|
FROM items
|
||||||
|
WHERE items.id = '554b11ce-fecb-4020-981e-acabbf7b5913'"
|
||||||
|
)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await?
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sqlx::test(fixtures(path = "../../../tests/fixtures", scripts("default")))]
|
||||||
|
async fn invalid_parameters(pool: sqlx::PgPool) -> sqlx::Result<()> {
|
||||||
|
let repo = ItemRepository::new(pool.clone());
|
||||||
|
|
||||||
|
// cycle
|
||||||
|
assert!(repo
|
||||||
|
.edit(
|
||||||
|
uuid!("4fc0f5f4-4dca-4c24-844d-1f464cb32afa"),
|
||||||
|
&ItemEditForm {
|
||||||
|
name: None,
|
||||||
|
parent: Some(uuid!("4fc0f5f4-4dca-4c24-844d-1f464cb32afa")),
|
||||||
|
class: uuid!("8a979306-b4c6-4ef8-900d-68f64abb2975"),
|
||||||
|
original_packaging: None,
|
||||||
|
description: "".to_string(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.is_err());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -21,3 +21,42 @@ impl ItemRepository {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use uuid::uuid;
|
||||||
|
|
||||||
|
use crate::database::ItemRepository;
|
||||||
|
use crate::label::LabelPage;
|
||||||
|
|
||||||
|
#[sqlx::test(fixtures(path = "../../../tests/fixtures", scripts("default")))]
|
||||||
|
async fn success_many(pool: sqlx::PgPool) -> sqlx::Result<()> {
|
||||||
|
let repo = ItemRepository::new(pool.clone());
|
||||||
|
|
||||||
|
let items = vec![
|
||||||
|
uuid!("4fc0f5f4-4dca-4c24-844d-1f464cb32afa"),
|
||||||
|
uuid!("049298e2-73db-42fb-957d-a741655648b1"),
|
||||||
|
uuid!("663f45e6-b11a-4197-8ce4-c784ac9ee617"),
|
||||||
|
];
|
||||||
|
|
||||||
|
let short_ids: Vec<i32> =
|
||||||
|
sqlx::query_scalar("SELECT short_id FROM items WHERE id = ANY ($1)")
|
||||||
|
.bind(&items)
|
||||||
|
.fetch_all(&pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
repo.label_pages(&items).await?,
|
||||||
|
items
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(idx, id)| LabelPage {
|
||||||
|
id: Some(id),
|
||||||
|
short_id: Some(format!("{:0>6}", short_ids[idx]))
|
||||||
|
})
|
||||||
|
.collect::<Vec<LabelPage>>()
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ use super::ItemRepository;
|
||||||
use super::{ItemName, ItemPreview};
|
use super::{ItemName, ItemPreview};
|
||||||
use crate::database::item_states::ItemState;
|
use crate::database::item_states::ItemState;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct ItemListEntry {
|
pub struct ItemListEntry {
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub name: ItemName,
|
pub name: ItemName,
|
||||||
|
@ -93,3 +94,60 @@ impl ItemRepository {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use uuid::uuid;
|
||||||
|
|
||||||
|
use super::ItemListEntry;
|
||||||
|
use crate::database::{
|
||||||
|
item_states::ItemState,
|
||||||
|
items::{ItemName, ItemPreview},
|
||||||
|
ItemRepository,
|
||||||
|
};
|
||||||
|
use crate::test_utils::*;
|
||||||
|
|
||||||
|
#[sqlx::test(fixtures(path = "../../../tests/fixtures", scripts("default")))]
|
||||||
|
async fn list(pool: sqlx::PgPool) -> sqlx::Result<()> {
|
||||||
|
let repo = ItemRepository::new(pool.clone());
|
||||||
|
|
||||||
|
let list = repo.list().await?;
|
||||||
|
|
||||||
|
assert_eq!(list.len(), item_count(&pool).await? as usize);
|
||||||
|
|
||||||
|
assert!(list.contains(&ItemListEntry {
|
||||||
|
id: uuid!("554b11ce-fecb-4020-981e-acabbf7b5913"),
|
||||||
|
name: ItemName::Item("Item 4".to_string()),
|
||||||
|
class: uuid!("e993e21c-8558-49e7-a993-2a6a61c1d55c"),
|
||||||
|
class_name: "Class 1".to_string(),
|
||||||
|
// actual content is tested in test for ItemRepository::parents
|
||||||
|
parents: repo
|
||||||
|
.parents(uuid!("554b11ce-fecb-4020-981e-acabbf7b5913"))
|
||||||
|
.await?,
|
||||||
|
state: ItemState::Owned,
|
||||||
|
}));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sqlx::test(fixtures(path = "../../../tests/fixtures", scripts("default")))]
|
||||||
|
async fn previews(pool: sqlx::PgPool) -> sqlx::Result<()> {
|
||||||
|
let repo = ItemRepository::new(pool.clone());
|
||||||
|
|
||||||
|
let previews = repo.previews().await?;
|
||||||
|
|
||||||
|
assert_eq!(previews.len(), item_count(&pool).await? as usize);
|
||||||
|
|
||||||
|
assert!(previews.contains(&ItemPreview::new(
|
||||||
|
uuid!("554b11ce-fecb-4020-981e-acabbf7b5913"),
|
||||||
|
ItemName::Item("Item 4".to_string())
|
||||||
|
)));
|
||||||
|
|
||||||
|
assert!(previews.contains(&ItemPreview::new(
|
||||||
|
uuid!("3003e61f-0824-4625-9b72-eeb9f11a6a26"),
|
||||||
|
ItemName::Class("Class 1".to_string())
|
||||||
|
)));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ impl ItemRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct ItemPreview {
|
pub struct ItemPreview {
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub name: ItemName,
|
pub name: ItemName,
|
||||||
|
@ -124,3 +125,169 @@ impl ItemRepository {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use quickcheck_macros::quickcheck;
|
||||||
|
use uuid::{uuid, Uuid};
|
||||||
|
|
||||||
|
use super::ItemPreview;
|
||||||
|
use crate::database::{item_states::ItemState, items::ItemName, ItemRepository};
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn item_preview_new(id: u128, name: ItemName) {
|
||||||
|
let id = Uuid::from_u128(id);
|
||||||
|
assert_eq!(
|
||||||
|
ItemPreview::new(id, name.clone()),
|
||||||
|
ItemPreview {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
state: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn item_preview_from_parts(id: u128, item_name: Option<String>, class_name: String) {
|
||||||
|
let id = Uuid::from_u128(id);
|
||||||
|
assert_eq!(
|
||||||
|
ItemPreview::from_parts(id, item_name.as_ref(), &class_name),
|
||||||
|
ItemPreview {
|
||||||
|
id,
|
||||||
|
name: ItemName::new(item_name.as_ref(), &class_name),
|
||||||
|
state: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn item_preview_with_state(id: u128, name: ItemName, state: ItemState) {
|
||||||
|
let id = Uuid::from_u128(id);
|
||||||
|
assert_eq!(
|
||||||
|
ItemPreview::new(id, name.clone()).with_state(state),
|
||||||
|
ItemPreview {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
state: Some(state),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sqlx::test(fixtures(path = "../../../tests/fixtures", scripts("default")))]
|
||||||
|
async fn parents_multiple(pool: sqlx::PgPool) -> sqlx::Result<()> {
|
||||||
|
let repo = ItemRepository::new(pool.clone());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
repo.parents(uuid!("554b11ce-fecb-4020-981e-acabbf7b5913"))
|
||||||
|
.await?,
|
||||||
|
vec![
|
||||||
|
ItemPreview::new(
|
||||||
|
uuid!("4fc0f5f4-4dca-4c24-844d-1f464cb32afa"),
|
||||||
|
ItemName::Item("Item 1".to_string())
|
||||||
|
),
|
||||||
|
ItemPreview::new(
|
||||||
|
uuid!("3003e61f-0824-4625-9b72-eeb9f11a6a26"),
|
||||||
|
ItemName::Class("Class 1".to_string())
|
||||||
|
),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sqlx::test(fixtures(path = "../../../tests/fixtures", scripts("default")))]
|
||||||
|
async fn parents_none(pool: sqlx::PgPool) -> sqlx::Result<()> {
|
||||||
|
let repo = ItemRepository::new(pool.clone());
|
||||||
|
|
||||||
|
assert!(repo
|
||||||
|
.parents(uuid!("4fc0f5f4-4dca-4c24-844d-1f464cb32afa"))
|
||||||
|
.await?
|
||||||
|
.is_empty());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sqlx::test(fixtures(path = "../../../tests/fixtures", scripts("default")))]
|
||||||
|
async fn children_multiple(pool: sqlx::PgPool) -> sqlx::Result<()> {
|
||||||
|
let repo = ItemRepository::new(pool.clone());
|
||||||
|
|
||||||
|
let children = repo
|
||||||
|
.children(uuid!("4fc0f5f4-4dca-4c24-844d-1f464cb32afa"))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let expected = [
|
||||||
|
ItemPreview::new(
|
||||||
|
uuid!("663f45e6-b11a-4197-8ce4-c784ac9ee617"),
|
||||||
|
ItemName::Item("Item 2".to_string()),
|
||||||
|
)
|
||||||
|
.with_state(ItemState::Owned),
|
||||||
|
ItemPreview::new(
|
||||||
|
uuid!("3003e61f-0824-4625-9b72-eeb9f11a6a26"),
|
||||||
|
ItemName::Class("Class 1".to_string()),
|
||||||
|
)
|
||||||
|
.with_state(ItemState::Owned),
|
||||||
|
];
|
||||||
|
|
||||||
|
assert_eq!(children.len(), expected.len());
|
||||||
|
|
||||||
|
// can’t use children == expected as order does not matter
|
||||||
|
assert!(children.iter().all(|child| expected.contains(child)));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sqlx::test(fixtures(path = "../../../tests/fixtures", scripts("default")))]
|
||||||
|
async fn children_none(pool: sqlx::PgPool) -> sqlx::Result<()> {
|
||||||
|
let repo = ItemRepository::new(pool.clone());
|
||||||
|
|
||||||
|
assert!(repo
|
||||||
|
.children(uuid!("554b11ce-fecb-4020-981e-acabbf7b5913"))
|
||||||
|
.await?
|
||||||
|
.is_empty());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sqlx::test(fixtures(path = "../../../tests/fixtures", scripts("default")))]
|
||||||
|
async fn original_packaging_of_multiple(pool: sqlx::PgPool) -> sqlx::Result<()> {
|
||||||
|
let repo = ItemRepository::new(pool.clone());
|
||||||
|
|
||||||
|
let original_packaging_of = repo
|
||||||
|
.original_packaging_of(uuid!("049298e2-73db-42fb-957d-a741655648b1"))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let expected = [
|
||||||
|
ItemPreview::new(
|
||||||
|
uuid!("663f45e6-b11a-4197-8ce4-c784ac9ee617"),
|
||||||
|
ItemName::Item("Item 2".to_string()),
|
||||||
|
)
|
||||||
|
.with_state(ItemState::Owned),
|
||||||
|
ItemPreview::new(
|
||||||
|
uuid!("4072791f-c5a0-41ac-9e63-2eb1d99b78de"),
|
||||||
|
ItemName::Item("Item 2 companion".to_string()),
|
||||||
|
)
|
||||||
|
.with_state(ItemState::Owned),
|
||||||
|
];
|
||||||
|
|
||||||
|
assert_eq!(original_packaging_of.len(), expected.len());
|
||||||
|
|
||||||
|
// can’t use original_packaging_of == expected as order does not matter
|
||||||
|
assert!(original_packaging_of
|
||||||
|
.iter()
|
||||||
|
.all(|child| expected.contains(child)));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sqlx::test(fixtures(path = "../../../tests/fixtures", scripts("default")))]
|
||||||
|
async fn original_packaging_of_none(pool: sqlx::PgPool) -> sqlx::Result<()> {
|
||||||
|
let repo = ItemRepository::new(pool.clone());
|
||||||
|
|
||||||
|
assert!(repo
|
||||||
|
.original_packaging_of(uuid!("4fc0f5f4-4dca-4c24-844d-1f464cb32afa"))
|
||||||
|
.await?
|
||||||
|
.is_empty());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,12 +2,14 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
use quickcheck::{Arbitrary, Gen};
|
||||||
use sqlx::query;
|
use sqlx::query;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::ItemRepository;
|
use super::ItemRepository;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum ItemName {
|
pub enum ItemName {
|
||||||
Item(String),
|
Item(String),
|
||||||
Class(String),
|
Class(String),
|
||||||
|
@ -31,6 +33,18 @@ impl ItemName {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
impl Arbitrary for ItemName {
|
||||||
|
fn arbitrary(g: &mut Gen) -> Self {
|
||||||
|
match u8::arbitrary(g) % 3 {
|
||||||
|
0 => Self::Item(String::arbitrary(g)),
|
||||||
|
1 => Self::Class(String::arbitrary(g)),
|
||||||
|
2 => Self::None,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ItemRepository {
|
impl ItemRepository {
|
||||||
pub async fn name(&self, id: Uuid) -> sqlx::Result<ItemName> {
|
pub async fn name(&self, id: Uuid) -> sqlx::Result<ItemName> {
|
||||||
query!(
|
query!(
|
||||||
|
@ -48,3 +62,46 @@ impl ItemRepository {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use uuid::uuid;
|
||||||
|
|
||||||
|
use super::ItemName;
|
||||||
|
use crate::database::ItemRepository;
|
||||||
|
use quickcheck_macros::quickcheck;
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn item_name_is_some(item_name: String, class_name: String) {
|
||||||
|
let name = ItemName::new(Some(&item_name), &class_name);
|
||||||
|
|
||||||
|
assert_eq!(name, ItemName::Item(item_name));
|
||||||
|
assert_eq!(name.clone().terse(), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[quickcheck]
|
||||||
|
fn item_name_is_none(class_name: String) {
|
||||||
|
let name = ItemName::new(None, &class_name);
|
||||||
|
|
||||||
|
assert_eq!(name, ItemName::Class(class_name));
|
||||||
|
assert_eq!(name.terse(), ItemName::None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sqlx::test(fixtures(path = "../../../tests/fixtures", scripts("default")))]
|
||||||
|
async fn success(pool: sqlx::PgPool) -> sqlx::Result<()> {
|
||||||
|
let repo = ItemRepository::new(pool.clone());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
repo.name(uuid!("4fc0f5f4-4dca-4c24-844d-1f464cb32afa"))
|
||||||
|
.await?,
|
||||||
|
ItemName::Item("Item 1".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
repo.name(uuid!("3003e61f-0824-4625-9b72-eeb9f11a6a26"))
|
||||||
|
.await?,
|
||||||
|
ItemName::Class("Class 1".to_string())
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,9 +5,10 @@
|
||||||
use sqlx::query;
|
use sqlx::query;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::{ItemRepository, ItemName, ItemPreview};
|
use super::{ItemName, ItemPreview, ItemRepository};
|
||||||
use crate::database::item_states::ItemState;
|
use crate::database::item_states::ItemState;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct ItemDetails {
|
pub struct ItemDetails {
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub short_id: i32,
|
pub short_id: i32,
|
||||||
|
@ -69,3 +70,50 @@ impl ItemRepository {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use sqlx::query_scalar;
|
||||||
|
use uuid::uuid;
|
||||||
|
|
||||||
|
use super::ItemDetails;
|
||||||
|
use crate::database::{
|
||||||
|
item_states::ItemState,
|
||||||
|
items::{ItemName, ItemPreview},
|
||||||
|
ItemRepository,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[sqlx::test(fixtures(path = "../../../tests/fixtures", scripts("default")))]
|
||||||
|
async fn success(pool: sqlx::PgPool) -> sqlx::Result<()> {
|
||||||
|
let repo = ItemRepository::new(pool.clone());
|
||||||
|
|
||||||
|
let short_id: i32 = query_scalar(
|
||||||
|
"SELECT short_id FROM items WHERE id = '663f45e6-b11a-4197-8ce4-c784ac9ee617'",
|
||||||
|
)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
repo.details(uuid!("663f45e6-b11a-4197-8ce4-c784ac9ee617"))
|
||||||
|
.await?,
|
||||||
|
ItemDetails {
|
||||||
|
id: uuid!("663f45e6-b11a-4197-8ce4-c784ac9ee617"),
|
||||||
|
short_id,
|
||||||
|
name: ItemName::Item("Item 2".to_string()),
|
||||||
|
class: uuid!("8a979306-b4c6-4ef8-900d-68f64abb2975"),
|
||||||
|
class_name: "Subclass 1.1".to_string(),
|
||||||
|
original_packaging: Some(
|
||||||
|
ItemPreview::new(
|
||||||
|
uuid!("049298e2-73db-42fb-957d-a741655648b1"),
|
||||||
|
ItemName::Item("Original Packaging of Item 2".to_string())
|
||||||
|
)
|
||||||
|
.with_state(ItemState::Owned)
|
||||||
|
),
|
||||||
|
description: "Lorem ipsum 3".to_string(),
|
||||||
|
state: ItemState::Owned,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -171,7 +171,7 @@ impl TextConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct LabelPage {
|
pub struct LabelPage {
|
||||||
pub id: Option<Uuid>,
|
pub id: Option<Uuid>,
|
||||||
pub short_id: Option<String>,
|
pub short_id: Option<String>,
|
||||||
|
|
|
@ -7,5 +7,7 @@ pub mod database;
|
||||||
pub mod frontend;
|
pub mod frontend;
|
||||||
pub mod label;
|
pub mod label;
|
||||||
pub mod middleware;
|
pub mod middleware;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_utils;
|
||||||
|
|
||||||
pub use config::Config;
|
pub use config::Config;
|
||||||
|
|
11
src/test_utils.rs
Normal file
11
src/test_utils.rs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// SPDX-FileCopyrightText: 2024 Simon Bruder <simon@sbruder.de>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
use sqlx::query_scalar;
|
||||||
|
|
||||||
|
pub async fn item_count(pool: &sqlx::PgPool) -> sqlx::Result<i64> {
|
||||||
|
query_scalar("SELECT count(id) FROM items")
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await
|
||||||
|
}
|
34
tests/fixtures/default.sql
vendored
Normal file
34
tests/fixtures/default.sql
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
-- SPDX-FileCopyrightText: 2024 Simon Bruder <simon@sbruder.de>
|
||||||
|
--
|
||||||
|
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
INSERT INTO item_classes (id, name, parent, description) VALUES
|
||||||
|
('e993e21c-8558-49e7-a993-2a6a61c1d55c', 'Class 1', NULL, 'Lorem ipsum 1'),
|
||||||
|
('04527cc8-2fbf-4a99-aa0a-361252c8f6d3', 'Class 2', NULL, 'Lorem ipsum 2'),
|
||||||
|
('9d760792-ddb0-47a0-bed1-c27dc41b285b', 'Class 3', NULL, 'Lorem ipsum 3'),
|
||||||
|
('8a979306-b4c6-4ef8-900d-68f64abb2975', 'Subclass 1.1', 'e993e21c-8558-49e7-a993-2a6a61c1d55c', 'Lorem ipsum 4'),
|
||||||
|
('042fe283-f645-401c-9079-3bd3ab1c3dc9', 'Subclass 1.2', 'e993e21c-8558-49e7-a993-2a6a61c1d55c', 'Lorem ipsum 5'),
|
||||||
|
('01ad10ec-d3be-4346-9d44-4ebb7297a14d', 'Subclass 2.1', '04527cc8-2fbf-4a99-aa0a-361252c8f6d3', 'Lorem ipsum 6');
|
||||||
|
|
||||||
|
INSERT INTO items (id, name, parent, class, original_packaging, description) VALUES
|
||||||
|
('4fc0f5f4-4dca-4c24-844d-1f464cb32afa', 'Item 1', NULL, 'e993e21c-8558-49e7-a993-2a6a61c1d55c', NULL, 'Lorem ipsum 1'),
|
||||||
|
('049298e2-73db-42fb-957d-a741655648b1', 'Original Packaging of Item 2', NULL, '01ad10ec-d3be-4346-9d44-4ebb7297a14d', NULL, 'Lorem ipsum 2'),
|
||||||
|
('663f45e6-b11a-4197-8ce4-c784ac9ee617', 'Item 2', '4fc0f5f4-4dca-4c24-844d-1f464cb32afa', '8a979306-b4c6-4ef8-900d-68f64abb2975', '049298e2-73db-42fb-957d-a741655648b1', 'Lorem ipsum 3'),
|
||||||
|
('4072791f-c5a0-41ac-9e63-2eb1d99b78de', 'Item 2 companion', '049298e2-73db-42fb-957d-a741655648b1', '042fe283-f645-401c-9079-3bd3ab1c3dc9', '049298e2-73db-42fb-957d-a741655648b1', 'Lorem ipsum 10'),
|
||||||
|
('3003e61f-0824-4625-9b72-eeb9f11a6a26', NULL, '4fc0f5f4-4dca-4c24-844d-1f464cb32afa', 'e993e21c-8558-49e7-a993-2a6a61c1d55c', NULL, 'Lorem ipsum 4'),
|
||||||
|
('554b11ce-fecb-4020-981e-acabbf7b5913', 'Item 4', '3003e61f-0824-4625-9b72-eeb9f11a6a26', 'e993e21c-8558-49e7-a993-2a6a61c1d55c', NULL, 'Lorem ipsum 5'),
|
||||||
|
('b9fce434-faa4-4242-bd06-9d3589fa41e7', 'Borrowed Item', NULL, '9d760792-ddb0-47a0-bed1-c27dc41b285b', NULL, 'Lorem ipsum 6'),
|
||||||
|
('2683d77f-2d9c-4a5c-b87f-6e1a99c69db0', 'Loaned Item', NULL, '9d760792-ddb0-47a0-bed1-c27dc41b285b', NULL, 'Lorem ipsum 7'),
|
||||||
|
('5ca9ed99-2e70-4723-9ae4-0bb5ab274366', 'Inactive Item', NULL, '9d760792-ddb0-47a0-bed1-c27dc41b285b', NULL, 'Lorem ipsum 8'),
|
||||||
|
('2da2643d-c759-48ab-8cdf-e4d46c8ecc69', 'Owned Item (bought)', NULL, '9d760792-ddb0-47a0-bed1-c27dc41b285b', NULL, 'Lorem ipsum 9');
|
||||||
|
|
||||||
|
DELETE FROM item_events WHERE item = ANY (ARRAY[
|
||||||
|
'b9fce434-faa4-4242-bd06-9d3589fa41e7',
|
||||||
|
'2da2643d-c759-48ab-8cdf-e4d46c8ecc69'
|
||||||
|
]::uuid[]);
|
||||||
|
|
||||||
|
INSERT INTO item_events (item, event, description) VALUES
|
||||||
|
('b9fce434-faa4-4242-bd06-9d3589fa41e7', 'borrow', 'from Jane Person'),
|
||||||
|
('2683d77f-2d9c-4a5c-b87f-6e1a99c69db0', 'loan', 'to Joe Person'),
|
||||||
|
('5ca9ed99-2e70-4723-9ae4-0bb5ab274366', 'gift', 'to Jude Person'),
|
||||||
|
('2da2643d-c759-48ab-8cdf-e4d46c8ecc69', 'buy', 'from garage sale');
|
Loading…
Reference in a new issue