feat: adds JiraDatabase with CRUD
This commit is contained in:
parent
148ca03686
commit
9ba820a43c
409
src/db.rs
409
src/db.rs
@ -1,9 +1,111 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{anyhow, Result};
|
||||||
|
|
||||||
use crate::models::{DBState, Epic, Status, Story};
|
use crate::models::{DBState, Epic, Status, Story};
|
||||||
|
|
||||||
|
pub struct JiraDatabase {
|
||||||
|
database: Box<dyn Database>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JiraDatabase {
|
||||||
|
pub fn new(file_path: String) -> Self {
|
||||||
|
Self {
|
||||||
|
database: Box::new(JSONFileDatabase { file_path }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_db(&self) -> Result<DBState> {
|
||||||
|
self.database.read_db()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_epic(&self, epic: Epic) -> Result<u32> {
|
||||||
|
let mut db = self.read_db()?;
|
||||||
|
|
||||||
|
let id = db.last_item_id + 1;
|
||||||
|
db.last_item_id = id;
|
||||||
|
db.epics.insert(id, epic);
|
||||||
|
self.database.write_db(&db)?;
|
||||||
|
Ok(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_story(&self, story: Story, epic_id: u32) -> Result<u32> {
|
||||||
|
let mut db = self.read_db()?;
|
||||||
|
|
||||||
|
let id = db.last_item_id + 1;
|
||||||
|
db.last_item_id = id;
|
||||||
|
db.stories.insert(id, story);
|
||||||
|
db.epics
|
||||||
|
.get_mut(&epic_id)
|
||||||
|
.ok_or_else(|| anyhow!(format!("epic not found: {epic_id}")))?
|
||||||
|
.stories
|
||||||
|
.push(id);
|
||||||
|
self.database.write_db(&db)?;
|
||||||
|
Ok(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_epic(&self, epic_id: u32) -> Result<()> {
|
||||||
|
let mut db = self.read_db()?;
|
||||||
|
|
||||||
|
let epic = db
|
||||||
|
.epics
|
||||||
|
.get(&epic_id)
|
||||||
|
.ok_or_else(|| anyhow!(format!("epic not found: {epic_id}")))?;
|
||||||
|
db.stories.retain(|k, _| !epic.stories.contains(k));
|
||||||
|
|
||||||
|
db.epics.retain(|k, _| k != &epic_id);
|
||||||
|
|
||||||
|
self.database.write_db(&db)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_story(&self, epic_id: u32, story_id: u32) -> Result<()> {
|
||||||
|
let mut db = self.read_db()?;
|
||||||
|
|
||||||
|
let epic = db
|
||||||
|
.epics
|
||||||
|
.get_mut(&epic_id)
|
||||||
|
.ok_or_else(|| anyhow!(format!("epic not found: {epic_id}")))?;
|
||||||
|
|
||||||
|
if !epic.stories.contains(&story_id) {
|
||||||
|
return Err(anyhow!(format!(
|
||||||
|
"story {story_id} not found in epic {epic_id}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
epic.stories.retain(|k| k != &story_id);
|
||||||
|
|
||||||
|
db.stories.retain(|k, _| k != &story_id);
|
||||||
|
|
||||||
|
self.database.write_db(&db)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_epic_status(&self, epic_id: u32, status: Status) -> Result<()> {
|
||||||
|
let mut db = self.read_db()?;
|
||||||
|
|
||||||
|
db.epics
|
||||||
|
.get_mut(&epic_id)
|
||||||
|
.ok_or_else(|| anyhow!(format!("epic not found: {epic_id}")))?
|
||||||
|
.status = status;
|
||||||
|
|
||||||
|
self.database.write_db(&db)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_story_status(&self, story_id: u32, status: Status) -> Result<()> {
|
||||||
|
let mut db = self.read_db()?;
|
||||||
|
|
||||||
|
db.stories
|
||||||
|
.get_mut(&story_id)
|
||||||
|
.ok_or_else(|| anyhow!(format!("story not found: {story_id}")))?
|
||||||
|
.status = status;
|
||||||
|
|
||||||
|
self.database.write_db(&db)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
trait Database {
|
trait Database {
|
||||||
fn read_db(&self) -> Result<DBState>;
|
fn read_db(&self) -> Result<DBState>;
|
||||||
fn write_db(&self, db_state: &DBState) -> Result<()>;
|
fn write_db(&self, db_state: &DBState) -> Result<()>;
|
||||||
@ -26,10 +128,314 @@ impl Database for JSONFileDatabase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod test_utils {
|
||||||
|
use std::{cell::RefCell, collections::HashMap};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub struct MockDB {
|
||||||
|
last_written_state: RefCell<DBState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MockDB {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
last_written_state: RefCell::new(DBState {
|
||||||
|
last_item_id: 0,
|
||||||
|
epics: HashMap::new(),
|
||||||
|
stories: HashMap::new(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Database for MockDB {
|
||||||
|
fn read_db(&self) -> Result<DBState> {
|
||||||
|
// TODO: fix this error by deriving the appropriate traits for Story
|
||||||
|
let state = self.last_written_state.borrow().clone();
|
||||||
|
Ok(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_db(&self, db_state: &DBState) -> Result<()> {
|
||||||
|
let latest_state = &self.last_written_state;
|
||||||
|
// TODO: fix this error by deriving the appropriate traits for DBState
|
||||||
|
*latest_state.borrow_mut() = db_state.clone();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::test_utils::MockDB;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn create_epic_should_work() {
|
||||||
|
let db = JiraDatabase {
|
||||||
|
database: Box::new(MockDB::new()),
|
||||||
|
};
|
||||||
|
let epic = Epic::new("".to_owned(), "".to_owned());
|
||||||
|
|
||||||
|
// TODO: fix this error by deriving the appropriate traits for Epic
|
||||||
|
let result = db.create_epic(epic.clone());
|
||||||
|
|
||||||
|
assert_eq!(result.is_ok(), true);
|
||||||
|
|
||||||
|
let id = result.unwrap();
|
||||||
|
let db_state = db.read_db().unwrap();
|
||||||
|
|
||||||
|
let expected_id = 1;
|
||||||
|
|
||||||
|
assert_eq!(id, expected_id);
|
||||||
|
assert_eq!(db_state.last_item_id, expected_id);
|
||||||
|
assert_eq!(db_state.epics.get(&id), Some(&epic));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn create_story_should_error_if_invalid_epic_id() {
|
||||||
|
let db = JiraDatabase {
|
||||||
|
database: Box::new(MockDB::new()),
|
||||||
|
};
|
||||||
|
let story = Story::new("".to_owned(), "".to_owned());
|
||||||
|
|
||||||
|
let non_existent_epic_id = 999;
|
||||||
|
|
||||||
|
let result = db.create_story(story, non_existent_epic_id);
|
||||||
|
assert_eq!(result.is_err(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn create_story_should_work() {
|
||||||
|
let db = JiraDatabase {
|
||||||
|
database: Box::new(MockDB::new()),
|
||||||
|
};
|
||||||
|
let epic = Epic::new("".to_owned(), "".to_owned());
|
||||||
|
let story = Story::new("".to_owned(), "".to_owned());
|
||||||
|
|
||||||
|
let result = db.create_epic(epic);
|
||||||
|
assert_eq!(result.is_ok(), true);
|
||||||
|
|
||||||
|
let epic_id = result.unwrap();
|
||||||
|
|
||||||
|
// TODO: fix this error by deriving the appropriate traits for Story
|
||||||
|
let result = db.create_story(story.clone(), epic_id);
|
||||||
|
assert_eq!(result.is_ok(), true);
|
||||||
|
|
||||||
|
let id = result.unwrap();
|
||||||
|
let db_state = db.read_db().unwrap();
|
||||||
|
|
||||||
|
let expected_id = 2;
|
||||||
|
|
||||||
|
assert_eq!(id, expected_id);
|
||||||
|
assert_eq!(db_state.last_item_id, expected_id);
|
||||||
|
assert_eq!(
|
||||||
|
db_state.epics.get(&epic_id).unwrap().stories.contains(&id),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
assert_eq!(db_state.stories.get(&id), Some(&story));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delete_epic_should_error_if_invalid_epic_id() {
|
||||||
|
let db = JiraDatabase {
|
||||||
|
database: Box::new(MockDB::new()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let non_existent_epic_id = 999;
|
||||||
|
|
||||||
|
let result = db.delete_epic(non_existent_epic_id);
|
||||||
|
assert_eq!(result.is_err(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delete_epic_should_work() {
|
||||||
|
let db = JiraDatabase {
|
||||||
|
database: Box::new(MockDB::new()),
|
||||||
|
};
|
||||||
|
let epic = Epic::new("".to_owned(), "".to_owned());
|
||||||
|
let story = Story::new("".to_owned(), "".to_owned());
|
||||||
|
|
||||||
|
let result = db.create_epic(epic);
|
||||||
|
assert_eq!(result.is_ok(), true);
|
||||||
|
|
||||||
|
let epic_id = result.unwrap();
|
||||||
|
|
||||||
|
let result = db.create_story(story, epic_id);
|
||||||
|
assert_eq!(result.is_ok(), true);
|
||||||
|
|
||||||
|
let story_id = result.unwrap();
|
||||||
|
|
||||||
|
let result = db.delete_epic(epic_id);
|
||||||
|
assert_eq!(result.is_ok(), true);
|
||||||
|
|
||||||
|
let db_state = db.read_db().unwrap();
|
||||||
|
|
||||||
|
let expected_last_id = 2;
|
||||||
|
|
||||||
|
assert_eq!(db_state.last_item_id, expected_last_id);
|
||||||
|
assert_eq!(db_state.epics.get(&epic_id), None);
|
||||||
|
assert_eq!(db_state.stories.get(&story_id), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delete_story_should_error_if_invalid_epic_id() {
|
||||||
|
let db = JiraDatabase {
|
||||||
|
database: Box::new(MockDB::new()),
|
||||||
|
};
|
||||||
|
let epic = Epic::new("".to_owned(), "".to_owned());
|
||||||
|
let story = Story::new("".to_owned(), "".to_owned());
|
||||||
|
|
||||||
|
let result = db.create_epic(epic);
|
||||||
|
assert_eq!(result.is_ok(), true);
|
||||||
|
|
||||||
|
let epic_id = result.unwrap();
|
||||||
|
|
||||||
|
let result = db.create_story(story, epic_id);
|
||||||
|
assert_eq!(result.is_ok(), true);
|
||||||
|
|
||||||
|
let story_id = result.unwrap();
|
||||||
|
|
||||||
|
let non_existent_epic_id = 999;
|
||||||
|
|
||||||
|
let result = db.delete_story(non_existent_epic_id, story_id);
|
||||||
|
assert_eq!(result.is_err(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delete_story_should_error_if_story_not_found_in_epic() {
|
||||||
|
let db = JiraDatabase {
|
||||||
|
database: Box::new(MockDB::new()),
|
||||||
|
};
|
||||||
|
let epic = Epic::new("".to_owned(), "".to_owned());
|
||||||
|
let story = Story::new("".to_owned(), "".to_owned());
|
||||||
|
|
||||||
|
let result = db.create_epic(epic);
|
||||||
|
assert_eq!(result.is_ok(), true);
|
||||||
|
|
||||||
|
let epic_id = result.unwrap();
|
||||||
|
|
||||||
|
let result = db.create_story(story, epic_id);
|
||||||
|
assert_eq!(result.is_ok(), true);
|
||||||
|
|
||||||
|
let non_existent_story_id = 999;
|
||||||
|
|
||||||
|
let result = db.delete_story(epic_id, non_existent_story_id);
|
||||||
|
assert_eq!(result.is_err(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delete_story_should_work() {
|
||||||
|
let db = JiraDatabase {
|
||||||
|
database: Box::new(MockDB::new()),
|
||||||
|
};
|
||||||
|
let epic = Epic::new("".to_owned(), "".to_owned());
|
||||||
|
let story = Story::new("".to_owned(), "".to_owned());
|
||||||
|
|
||||||
|
let result = db.create_epic(epic);
|
||||||
|
assert_eq!(result.is_ok(), true);
|
||||||
|
|
||||||
|
let epic_id = result.unwrap();
|
||||||
|
|
||||||
|
let result = db.create_story(story, epic_id);
|
||||||
|
assert_eq!(result.is_ok(), true);
|
||||||
|
|
||||||
|
let story_id = result.unwrap();
|
||||||
|
|
||||||
|
let result = db.delete_story(epic_id, story_id);
|
||||||
|
assert_eq!(result.is_ok(), true);
|
||||||
|
|
||||||
|
let db_state = db.read_db().unwrap();
|
||||||
|
|
||||||
|
let expected_last_id = 2;
|
||||||
|
|
||||||
|
assert_eq!(db_state.last_item_id, expected_last_id);
|
||||||
|
assert_eq!(
|
||||||
|
db_state
|
||||||
|
.epics
|
||||||
|
.get(&epic_id)
|
||||||
|
.unwrap()
|
||||||
|
.stories
|
||||||
|
.contains(&story_id),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
assert_eq!(db_state.stories.get(&story_id), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn update_epic_status_should_error_if_invalid_epic_id() {
|
||||||
|
let db = JiraDatabase {
|
||||||
|
database: Box::new(MockDB::new()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let non_existent_epic_id = 999;
|
||||||
|
|
||||||
|
let result = db.update_epic_status(non_existent_epic_id, Status::Closed);
|
||||||
|
assert_eq!(result.is_err(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn update_epic_status_should_work() {
|
||||||
|
let db = JiraDatabase {
|
||||||
|
database: Box::new(MockDB::new()),
|
||||||
|
};
|
||||||
|
let epic = Epic::new("".to_owned(), "".to_owned());
|
||||||
|
|
||||||
|
let result = db.create_epic(epic);
|
||||||
|
|
||||||
|
assert_eq!(result.is_ok(), true);
|
||||||
|
|
||||||
|
let epic_id = result.unwrap();
|
||||||
|
|
||||||
|
let result = db.update_epic_status(epic_id, Status::Closed);
|
||||||
|
|
||||||
|
assert_eq!(result.is_ok(), true);
|
||||||
|
|
||||||
|
let db_state = db.read_db().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(db_state.epics.get(&epic_id).unwrap().status, Status::Closed);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn update_story_status_should_error_if_invalid_story_id() {
|
||||||
|
let db = JiraDatabase {
|
||||||
|
database: Box::new(MockDB::new()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let non_existent_story_id = 999;
|
||||||
|
|
||||||
|
let result = db.update_story_status(non_existent_story_id, Status::Closed);
|
||||||
|
assert_eq!(result.is_err(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn update_story_status_should_work() {
|
||||||
|
let db = JiraDatabase {
|
||||||
|
database: Box::new(MockDB::new()),
|
||||||
|
};
|
||||||
|
let epic = Epic::new("".to_owned(), "".to_owned());
|
||||||
|
let story = Story::new("".to_owned(), "".to_owned());
|
||||||
|
|
||||||
|
let result = db.create_epic(epic);
|
||||||
|
|
||||||
|
let epic_id = result.unwrap();
|
||||||
|
|
||||||
|
let result = db.create_story(story, epic_id);
|
||||||
|
|
||||||
|
let story_id = result.unwrap();
|
||||||
|
|
||||||
|
let result = db.update_story_status(story_id, Status::Closed);
|
||||||
|
|
||||||
|
assert_eq!(result.is_ok(), true);
|
||||||
|
|
||||||
|
let db_state = db.read_db().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
db_state.stories.get(&story_id).unwrap().status,
|
||||||
|
Status::Closed
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
mod database {
|
mod database {
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
@ -127,7 +533,6 @@ mod tests {
|
|||||||
let read_result = db.read_db().unwrap();
|
let read_result = db.read_db().unwrap();
|
||||||
|
|
||||||
assert_eq!(write_result.is_ok(), true);
|
assert_eq!(write_result.is_ok(), true);
|
||||||
// TODO: fix this error by deriving the appropriate traits for DBState
|
|
||||||
assert_eq!(read_result, state);
|
assert_eq!(read_result, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum Status {
|
pub enum Status {
|
||||||
Open,
|
Open,
|
||||||
InProgress,
|
InProgress,
|
||||||
@ -9,7 +9,7 @@ pub enum Status {
|
|||||||
Closed,
|
Closed,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct Epic {
|
pub struct Epic {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
@ -28,7 +28,7 @@ impl Epic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct Story {
|
pub struct Story {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
@ -45,7 +45,7 @@ impl Story {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct DBState {
|
pub struct DBState {
|
||||||
pub last_item_id: u32,
|
pub last_item_id: u32,
|
||||||
pub epics: HashMap<u32, Epic>,
|
pub epics: HashMap<u32, Epic>,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user