use bms_table::{BmsTable, BmsTableData, BmsTableHeader, CourseGroup, CourseInfo};
use serde_json::json;
use std::collections::BTreeMap;
#[test]
fn test_build_bms_table_from_json() {
let header_json = json!({
"name": "Test Table",
"symbol": "test",
"data_url": "charts.json",
"course": [
[
{
"name": "Test Course",
"constraint": ["grade_mirror"],
"trophy": [
{
"name": "goldmedal",
"missrate": 1.0,
"scorerate": 90.0
}
],
"md5": ["test_md5_1", "test_md5_2"]
}
]
],
"level_order": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, "!i"],
"extra_field": "extra_value",
"another_field": 123
});
let data_json = json!([
{
"level": "1",
"id": 1,
"md5": "test_md5_1",
"sha256": "test_sha256_1",
"title": "Test Song",
"artist": "Test Artist",
"url": "https://example.com/test.bms",
"url_diff": "https://example.com/test_diff.bms",
"custom_field": "custom_value",
"rating": 5.0
}
]);
let header: BmsTableHeader = serde_json::from_value(header_json).unwrap();
let data: BmsTableData = serde_json::from_value(data_json).unwrap();
let bms_table = BmsTable { header, data };
assert_eq!(bms_table.header.name, "Test Table");
assert_eq!(bms_table.header.symbol, "test");
assert_eq!(bms_table.header.data_url, "charts.json");
assert_eq!(bms_table.data.charts.len(), 1);
let course = match &bms_table.header.course {
CourseGroup::SubGroups(groups) => {
let [group] = groups.as_slice() else {
panic!("expected one group, got {}: {:?}", groups.len(), groups);
};
match group {
CourseGroup::Courses(courses) => {
let [course] = courses.as_slice() else {
panic!("expected one course, got {}: {:?}", courses.len(), courses);
};
course
}
other => panic!("expected Courses, got {:?}", other),
}
}
other => panic!("expected SubGroups, got {:?}", other),
};
assert_eq!(course.name, "Test Course");
assert_eq!(course.constraint, vec!["grade_mirror"]);
assert_eq!(course.trophy.len(), 1);
let [trophy0] = course.trophy.as_slice() else {
panic!(
"expected one trophy, got {}: {:?}",
course.trophy.len(),
course.trophy
);
};
assert_eq!(trophy0.name, "goldmedal");
assert!((trophy0.missrate - 1.0).abs() <= 1e-12);
assert!((trophy0.scorerate - 90.0).abs() <= 1e-12);
assert_eq!(course.charts.len(), 2);
let [c0, c1] = course.charts.as_slice() else {
panic!(
"expected two charts, got {}: {:?}",
course.charts.len(),
course.charts
);
};
assert_eq!(c0.md5.as_deref(), Some("test_md5_1"));
assert_eq!(c1.md5.as_deref(), Some("test_md5_2"));
let [score] = bms_table.data.charts.as_slice() else {
panic!(
"expected one chart, got {}: {:?}",
bms_table.data.charts.len(),
bms_table.data.charts
);
};
assert_eq!(score.level, "1");
assert_eq!(score.md5, Some("test_md5_1".to_string()));
assert_eq!(score.sha256, Some("test_sha256_1".to_string()));
assert_eq!(score.title, Some("Test Song".to_string()));
assert_eq!(score.artist, Some("Test Artist".to_string()));
assert_eq!(score.url, Some("https://example.com/test.bms".to_string()));
assert_eq!(
score.url_diff,
Some("https://example.com/test_diff.bms".to_string())
);
assert_eq!(
bms_table.header.extra.get("extra_field"),
Some(&json!("extra_value"))
);
assert_eq!(
bms_table.header.extra.get("another_field"),
Some(&json!(123))
);
assert!(!bms_table.header.extra.contains_key("name"));
assert_eq!(
score.extra.get("custom_field"),
Some(&json!("custom_value"))
);
assert_eq!(score.extra.get("rating"), Some(&json!(5.0)));
assert!(!score.extra.contains_key("level"));
assert_eq!(bms_table.header.level_order.len(), 22);
let [first, .., penultimate, last] = bms_table.header.level_order.as_slice() else {
panic!(
"expected at least two level_order entries, got {}: {:?}",
bms_table.header.level_order.len(),
bms_table.header.level_order
);
};
assert_eq!(first.as_str(), "0");
assert_eq!(penultimate.as_str(), "20");
assert_eq!(last.as_str(), "!i");
assert!(!bms_table.header.extra.contains_key("level_order"));
}
#[test]
fn test_build_bms_table_with_empty_fields() {
let header_json = json!({
"name": "Test Table",
"symbol": "test",
"data_url": "charts.json",
"course": []
});
let data_json = json!([
{
"level": "1",
"id": 1,
"md5": "",
"sha256": "",
"title": "",
"artist": "",
"url": "",
"url_diff": ""
}
]);
let header: BmsTableHeader = serde_json::from_value(header_json).unwrap();
let data: BmsTableData = serde_json::from_value(data_json).unwrap();
let bms_table = BmsTable { header, data };
let [score] = bms_table.data.charts.as_slice() else {
panic!(
"expected one chart, got {}: {:?}",
bms_table.data.charts.len(),
bms_table.data.charts
);
};
assert_eq!(score.level, "1");
assert_eq!(score.md5, Some("".to_string()));
assert_eq!(score.sha256, Some("".to_string()));
assert_eq!(score.title, Some("".to_string()));
assert_eq!(score.artist, Some("".to_string()));
assert_eq!(score.url, Some("".to_string()));
assert_eq!(score.url_diff, Some("".to_string()));
}
#[test]
fn test_bms_table_creation() {
let header = BmsTableHeader {
name: "Test Table".to_string(),
symbol: "test".to_string(),
data_url: "https://example.com/charts.json".to_string(),
tag: None,
mode: None,
course: CourseGroup::Courses(vec![]),
level_order: vec!["0".to_string(), "1".to_string()],
extra: BTreeMap::new(),
};
let data = BmsTableData { charts: vec![] };
let bms_table = BmsTable { header, data };
assert_eq!(bms_table.header.name, "Test Table");
assert_eq!(bms_table.header.symbol, "test");
assert!(bms_table.header.course.flatten().is_empty());
assert_eq!(bms_table.data.charts.len(), 0);
assert_eq!(bms_table.header.level_order.len(), 2);
}
#[test]
fn test_bms_table_partial_eq() {
let header1 = BmsTableHeader {
name: "Test Table".to_string(),
symbol: "test".to_string(),
data_url: "https://example.com/charts.json".to_string(),
tag: None,
mode: None,
course: CourseGroup::Courses(vec![]),
level_order: vec!["0".to_string(), "1".to_string()],
extra: BTreeMap::new(),
};
let data1 = BmsTableData { charts: vec![] };
let table1 = BmsTable {
header: header1.clone(),
data: data1,
};
let header2 = header1;
let data2 = BmsTableData { charts: vec![] };
let table2 = BmsTable {
header: header2,
data: data2,
};
assert_eq!(table1, table2);
}
#[test]
fn test_chart_item_numeric_fields_to_string() {
let data_json = json!([
{
"level": 0,
"id": 1,
"md5": "12345",
"sha256": "67890",
"title": "987",
"artist": "654",
"url": "321",
"url_diff": "111"
}
]);
let data: BmsTableData = serde_json::from_value(data_json).unwrap();
let [score] = data.charts.as_slice() else {
panic!(
"expected one chart, got {}: {:?}",
data.charts.len(),
data.charts
);
};
assert_eq!(score.level, "0");
assert_eq!(score.md5, Some("12345".to_string()));
assert_eq!(score.sha256, Some("67890".to_string()));
assert_eq!(score.title, Some("987".to_string()));
assert_eq!(score.artist, Some("654".to_string()));
assert_eq!(score.url, Some("321".to_string()));
assert_eq!(score.url_diff, Some("111".to_string()));
}
#[test]
fn test_build_bms_table_invalid_json() {
let header_json = json!({
"name": "Test Table",
"symbol": "test",
"data_url": "charts.json"
});
let data_json = json!([
{
"level": "1",
"id": 1
}
]);
let header: BmsTableHeader = serde_json::from_value(header_json).unwrap();
let data: BmsTableData = serde_json::from_value(data_json).unwrap();
let _bms_table = BmsTable { header, data };
}
#[test]
fn test_bms_table_header_deserialize_vec_course_info() {
let json_data = r#"{
"name": "Test Table",
"symbol": "test",
"data_url": "score.json",
"course": [
{
"name": "Course 1",
"constraint": ["grade_mirror"],
"trophy": [
{
"name": "goldmedal",
"missrate": 5.0,
"scorerate": 70.0
}
],
"md5": ["abc123", "def456"]
}
]
}"#;
let result: serde_json::Result<BmsTableHeader> = serde_json::from_str(json_data);
assert!(result.is_ok());
let header = result.unwrap();
assert_eq!(header.name, "Test Table");
assert_eq!(header.symbol, "test");
assert_eq!(header.data_url, "score.json");
let course = match &header.course {
CourseGroup::Courses(courses) => {
let [course] = courses.as_slice() else {
panic!("expected one course, got {}: {:?}", courses.len(), courses);
};
course
}
other => panic!("expected Courses, got {:?}", other),
};
assert_eq!(course.name.as_str(), "Course 1");
assert_eq!(course.charts.len(), 2);
let [c0, c1] = course.charts.as_slice() else {
panic!(
"expected two charts, got {}: {:?}",
course.charts.len(),
course.charts
);
};
assert_eq!(c0.md5.as_deref(), Some("abc123"));
assert_eq!(c1.md5.as_deref(), Some("def456"));
}
#[test]
fn test_bms_table_header_deserialize_vec_vec_course_info() {
let json_data = r#"{
"name": "Test Table",
"symbol": "test",
"data_url": "score.json",
"course": [
[
{
"name": "Course 1",
"constraint": ["grade_mirror"],
"trophy": [
{
"name": "goldmedal",
"missrate": 5.0,
"scorerate": 70.0
}
],
"md5": ["abc123", "def456"]
}
],
[
{
"name": "Course 2",
"constraint": ["ln"],
"trophy": [
{
"name": "silvermedal",
"missrate": 10.0,
"scorerate": 60.0
}
],
"md5": ["ghi789"]
}
]
]
}"#;
let result: serde_json::Result<BmsTableHeader> = serde_json::from_str(json_data);
assert!(result.is_ok());
let header = result.unwrap();
assert_eq!(header.name, "Test Table");
assert_eq!(header.symbol, "test");
assert_eq!(header.data_url, "score.json");
let (course1, course2) = match &header.course {
CourseGroup::SubGroups(groups) => {
let [g1, g2] = groups.as_slice() else {
panic!("expected two groups, got {}: {:?}", groups.len(), groups);
};
let info1 = match g1 {
CourseGroup::Courses(courses) => {
let [c] = courses.as_slice() else {
panic!("expected one course, got {}: {:?}", courses.len(), courses);
};
c
}
other => panic!("expected Courses, got {:?}", other),
};
let info2 = match g2 {
CourseGroup::Courses(courses) => {
let [c] = courses.as_slice() else {
panic!("expected one course, got {}: {:?}", courses.len(), courses);
};
c
}
other => panic!("expected Courses, got {:?}", other),
};
(info1, info2)
}
other => panic!("expected SubGroups, got {:?}", other),
};
assert_eq!(course1.name.as_str(), "Course 1");
assert_eq!(course2.name.as_str(), "Course 2");
assert_eq!(course1.charts.len(), 2);
let [c10, c11] = course1.charts.as_slice() else {
panic!(
"expected two charts, got {}: {:?}",
course1.charts.len(),
course1.charts
);
};
assert_eq!(c10.md5.as_deref(), Some("abc123"));
assert_eq!(c11.md5.as_deref(), Some("def456"));
assert_eq!(course2.charts.len(), 1);
let [c20] = course2.charts.as_slice() else {
panic!(
"expected one chart, got {}: {:?}",
course2.charts.len(),
course2.charts
);
};
assert_eq!(c20.md5.as_deref(), Some("ghi789"));
}
#[test]
fn test_course_info_deserialize_charts_with_default_level() {
let json_data = r#"{
"name": "Test Course",
"constraint": ["grade_mirror"],
"trophy": [
{
"name": "goldmedal",
"missrate": 5.0,
"scorerate": 70.0
}
],
"charts": [
{
"title": "Test Song",
"artist": "Test Artist",
"url": "https://example.com/test.bms"
},
{
"level": "1",
"title": "Test Song 2",
"artist": "Test Artist 2",
"url": "https://example.com/test2.bms"
}
]
}"#;
let result: serde_json::Result<CourseInfo> = serde_json::from_str(json_data);
assert!(result.is_ok());
let course_info = result.unwrap();
assert_eq!(course_info.name, "Test Course");
assert_eq!(course_info.constraint, vec!["grade_mirror"]);
assert_eq!(course_info.trophy.len(), 1);
assert_eq!(course_info.charts.len(), 2);
let [first_chart, second_chart] = course_info.charts.as_slice() else {
panic!(
"expected two charts, got {}: {:?}",
course_info.charts.len(),
course_info.charts
);
};
assert_eq!(first_chart.level, "0");
assert_eq!(first_chart.title, Some("Test Song".to_string()));
assert_eq!(first_chart.artist, Some("Test Artist".to_string()));
assert_eq!(second_chart.level, "1");
assert_eq!(second_chart.title, Some("Test Song 2".to_string()));
assert_eq!(second_chart.artist, Some("Test Artist 2".to_string()));
}
#[test]
fn test_course_info_deserialize_sha256list_to_charts() {
let json_data = r#"{
"name": "Test Course",
"constraint": ["grade_mirror"],
"trophy": [
{
"name": "goldmedal",
"missrate": 5.0,
"scorerate": 70.0
}
],
"sha256": ["sha256_hash_1", "sha256_hash_2"]
}"#;
let result: serde_json::Result<CourseInfo> = serde_json::from_str(json_data);
assert!(result.is_ok());
let course_info = result.unwrap();
assert_eq!(course_info.name, "Test Course");
assert_eq!(course_info.constraint, vec!["grade_mirror"]);
assert_eq!(course_info.trophy.len(), 1);
assert_eq!(course_info.charts.len(), 2);
let [c0, c1] = course_info.charts.as_slice() else {
panic!(
"expected two charts, got {}: {:?}",
course_info.charts.len(),
course_info.charts
);
};
assert_eq!(c0.sha256.as_deref(), Some("sha256_hash_1"));
assert_eq!(c1.sha256.as_deref(), Some("sha256_hash_2"));
assert_eq!(c0.md5.as_deref(), None);
assert_eq!(c1.md5.as_deref(), None);
}
#[test]
fn test_course_info_deserialize_md5_and_sha256_to_charts() {
let json_data = r#"{
"name": "Test Course",
"constraint": ["grade_mirror"],
"trophy": [
{
"name": "goldmedal",
"missrate": 5.0,
"scorerate": 70.0
}
],
"md5": ["md5_hash_1"],
"sha256": ["sha256_hash_1"],
"charts": [
{
"level": "2",
"title": "Existing Chart",
"artist": "Test Artist"
}
]
}"#;
let result: serde_json::Result<CourseInfo> = serde_json::from_str(json_data);
assert!(result.is_ok());
let course_info = result.unwrap();
assert_eq!(course_info.name, "Test Course");
assert_eq!(course_info.constraint, vec!["grade_mirror"]);
assert_eq!(course_info.trophy.len(), 1);
assert_eq!(course_info.charts.len(), 3);
let [existing, from_md5, from_sha256] = course_info.charts.as_slice() else {
panic!(
"expected three charts, got {}: {:?}",
course_info.charts.len(),
course_info.charts
);
};
assert_eq!(existing.level.as_str(), "2");
assert_eq!(existing.title.as_deref(), Some("Existing Chart"));
assert_eq!(existing.artist.as_deref(), Some("Test Artist"));
assert_eq!(from_md5.md5.as_deref(), Some("md5_hash_1"));
assert_eq!(from_md5.level.as_str(), "0");
assert_eq!(from_sha256.sha256.as_deref(), Some("sha256_hash_1"));
assert_eq!(from_sha256.level.as_str(), "0");
}
#[test]
fn test_json_serialization() {
let header = bms_table::BmsTableHeader {
name: "Test Table".to_string(),
symbol: "test".to_string(),
data_url: "charts.json".to_string(),
tag: None,
mode: None,
course: CourseGroup::Courses(vec![]),
level_order: vec!["0".to_string(), "1".to_string(), "!i".to_string()],
extra: BTreeMap::new(),
};
let json = serde_json::to_string(&header).unwrap();
let parsed: bms_table::BmsTableHeader = serde_json::from_str(&json).unwrap();
assert_eq!(header, parsed);
assert!(parsed.course.flatten().is_empty());
}
fn make_course_info(name: &str) -> serde_json::Value {
json!({
"name": name,
"constraint": [],
"trophy": [],
"charts": [],
})
}
#[test]
fn roundtrip_course_empty_flat() {
let raw = json!({"name":"T","symbol":"t","data_url":"c.json","course":[],"level_order":[]});
let h: BmsTableHeader = serde_json::from_value(raw).unwrap();
assert!(matches!(&h.course, CourseGroup::Courses(v) if v.is_empty()));
let out = serde_json::to_value(&h).unwrap();
assert_eq!(out["course"], json!([]));
}
#[test]
fn roundtrip_course_single_flat() {
let course = json!([make_course_info("C1")]);
let raw = json!({"name":"T","symbol":"t","data_url":"c.json","course":course,"level_order":[]});
let h: BmsTableHeader = serde_json::from_value(raw).unwrap();
assert!(matches!(&h.course, CourseGroup::Courses(v) if v.len() == 1));
let out = serde_json::to_value(&h).unwrap();
assert_eq!(out["course"], course);
}
#[test]
fn roundtrip_course_multi_flat() {
let course = json!([make_course_info("C1"), make_course_info("C2")]);
let raw = json!({"name":"T","symbol":"t","data_url":"c.json","course":course,"level_order":[]});
let h: BmsTableHeader = serde_json::from_value(raw).unwrap();
assert!(matches!(&h.course, CourseGroup::Courses(v) if v.len() == 2));
let out = serde_json::to_value(&h).unwrap();
assert_eq!(out["course"], course);
}
#[test]
fn roundtrip_course_single_nested() {
let course = json!([[make_course_info("C1")]]);
let raw = json!({"name":"T","symbol":"t","data_url":"c.json","course":course,"level_order":[]});
let h: BmsTableHeader = serde_json::from_value(raw).unwrap();
assert!(matches!(&h.course, CourseGroup::SubGroups(g) if g.len() == 1));
let out = serde_json::to_value(&h).unwrap();
assert_eq!(out["course"], course);
}
#[test]
fn roundtrip_course_multi_nested() {
let course = json!([[make_course_info("C1")], [make_course_info("C2")]]);
let raw = json!({"name":"T","symbol":"t","data_url":"c.json","course":course,"level_order":[]});
let h: BmsTableHeader = serde_json::from_value(raw).unwrap();
assert!(matches!(&h.course, CourseGroup::SubGroups(g) if g.len() == 2));
let out = serde_json::to_value(&h).unwrap();
assert_eq!(out["course"], course);
}
#[test]
fn roundtrip_course_empty_nested() {
let course = json!([[]]);
let raw = json!({"name":"T","symbol":"t","data_url":"c.json","course":course,"level_order":[]});
let h: BmsTableHeader = serde_json::from_value(raw).unwrap();
assert!(
matches!(&h.course, CourseGroup::SubGroups(g) if matches!(&g[0], CourseGroup::Courses(v) if v.is_empty()))
);
let out = serde_json::to_value(&h).unwrap();
assert_eq!(out["course"], course);
}
#[test]
fn roundtrip_course_deeply_nested() {
let course = json!([[[make_course_info("C1")]]]);
let raw = json!({"name":"T","symbol":"t","data_url":"c.json","course":course,"level_order":[]});
let h: BmsTableHeader = serde_json::from_value(raw).unwrap();
assert!(matches!(&h.course, CourseGroup::SubGroups(g) if g.len() == 1));
let out = serde_json::to_value(&h).unwrap();
assert_eq!(out["course"], course);
}
#[test]
fn roundtrip_course_missing_defaults_to_empty() {
let raw = json!({"name":"T","symbol":"t","data_url":"c.json","level_order":[]});
let h: BmsTableHeader = serde_json::from_value(raw).unwrap();
assert!(h.course.flatten().is_empty());
assert!(matches!(&h.course, CourseGroup::Courses(v) if v.is_empty()));
}