Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 19 additions & 13 deletions atcoder-problems-backend/src/crawler_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,19 +253,25 @@ async fn upsert_problems(
name: Set(problem.name.clone()),
title: Set(title),
};
sql_entities::problems::Entity::insert(model)
.on_conflict(
OnConflict::column(sql_entities::problems::Column::Id)
.update_columns([
sql_entities::problems::Column::ContestId,
sql_entities::problems::Column::ProblemIndex,
sql_entities::problems::Column::Name,
sql_entities::problems::Column::Title,
])
.to_owned(),
)
.exec(db)
.await?;

// ADTの問題は全てABCで既出であり、これらは同一視したい。
// そのまま通すと問題情報がABCからADTに上書きされてしまうので、ADTの問題はproblemsには入れないようにする。
// (ただしcontest_problemには入れる)
if !problem.contest_id.starts_with("adt") {
sql_entities::problems::Entity::insert(model)
.on_conflict(
OnConflict::column(sql_entities::problems::Column::Id)
.update_columns([
sql_entities::problems::Column::ContestId,
sql_entities::problems::Column::ProblemIndex,
sql_entities::problems::Column::Name,
sql_entities::problems::Column::Title,
])
.to_owned(),
)
.exec(db)
.await?;
}
Comment on lines +260 to +274

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

例えば将来 ADT 以外に既出コンテストが始まったときに追加の修正が必要になってしまうので、「最初に出題されたコンテストを優先する」みたいな一般的な実装にできたりします?


// Insert into contest_problem table
let contest_problem = sql_entities::contest_problem::ActiveModel {
Expand Down
90 changes: 90 additions & 0 deletions atcoder-problems-backend/tests/test_crawler_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,3 +402,93 @@ async fn test_crawl_contests_fetches_filtered_archive_categories() {
.any(|contest| contest.id == "adt_all_20260612_2")
);
}

#[tokio::test]
async fn test_crawl_adt_problems() {
let db = setup_db().await.unwrap();

// 1. ABCのコンテスト、問題がクロールされる
sql_entities::contests::Entity::insert(sql_entities::contests::ActiveModel {
id: Set("abc001".to_string()),
start_epoch_second: Set(0),
duration_second: Set(0),
title: Set("AtCoder Beginner Contest 001".to_string()),
rate_change: Set("?".to_string()),
})
.exec(&db)
.await
.unwrap();

let mut abc_fetcher = MockProblemFetcher::new();
abc_fetcher
.expect_fetch_problems()
.withf(|contest_id| contest_id == "abc001")
.times(1)
.returning(|_| {
Ok(vec![Problem {
id: "abc001_a".to_string(),
contest_id: "abc001".to_string(),
problem_index: "A".to_string(),
name: "Problem A".to_string(),
}])
});

atcoder_problems_backend::crawler_utils::crawl_problems(&abc_fetcher, &db)
.await
.unwrap();

// 2. 次に、ADTのコンテスト、問題がクロールされる
sql_entities::contests::Entity::insert(sql_entities::contests::ActiveModel {
id: Set("adt_all_20260612".to_string()),
start_epoch_second: Set(0),
duration_second: Set(0),
title: Set("AtCoder Daily Training 2026/06/12 All".to_string()),
rate_change: Set("-".to_string()),
})
.exec(&db)
.await
.unwrap();

let mut adt_fetcher = MockProblemFetcher::new();
adt_fetcher
.expect_fetch_problems()
.withf(|contest_id| contest_id == "adt_all_20260612")
.times(1)
.returning(|_| {
Ok(vec![Problem {
id: "abc001_a".to_string(),
contest_id: "adt_all_20260612".to_string(),
problem_index: "C".to_string(),
name: "Problem C".to_string(),
}])
});

atcoder_problems_backend::crawler_utils::crawl_problems(&adt_fetcher, &db)
.await
.unwrap();

// 3. abc001_aがabc001とadt_all_20260612の両方に紐づいていることを確認する
let problems_after_adt = sql_entities::problems::Entity::find()
.all(&db)
.await
.unwrap();
assert_eq!(problems_after_adt.len(), 1);
assert_eq!(problems_after_adt[0].id, "abc001_a");
assert_eq!(problems_after_adt[0].contest_id, "abc001");

let contest_problems = sql_entities::contest_problem::Entity::find()
.all(&db)
.await
.unwrap();
assert_eq!(contest_problems.len(), 2);
assert!(contest_problems.iter().any(|cp| cp.contest_id == "abc001"
&& cp.problem_id == "abc001_a"
&& cp.problem_index == "A"));
assert!(
contest_problems
.iter()
.any(|cp| cp.contest_id == "adt_all_20260612"
&& cp.problem_id == "abc001_a"
&& cp.problem_index == "C")
);
}
Loading