Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

564 linesCodeBlameRaw
1use g1t_contracts::work::{SignalAuthor, SignalDay, SignalPull, SignalWeek, WaitingPull, WorkspaceSignalsArgs};
2use rusqlite::types::ValueRef;
3use rusqlite::{Connection, ToSql, params};
4use serde::de::DeserializeOwned;
5
6use super::{AUTHORS, DAYS, PULLS, PullRow, WAITING, WEEKS, WaitingRow, Window, is_person};
7
8const IDS: &str = r#"["a"]"#;
9
10fn schema() -> Connection {
11 let db = Connection::open_in_memory().unwrap();
12 db.execute_batch(
13 "CREATE TABLE pulls (id TEXT, repo_id TEXT, number INTEGER, title TEXT DEFAULT 'A change', status TEXT,
14 item_id TEXT, milestone INTEGER, agent TEXT DEFAULT 'person', author_id TEXT DEFAULT 'usr_ana',
15 author_name TEXT DEFAULT 'ana', requested_by_id TEXT, requested_by_name TEXT,
16 source_branch TEXT, fork_repo_id TEXT, head_repo_id TEXT,
17 created_at TEXT, updated_at TEXT, merged_at TEXT);
18 CREATE TABLE comments (repo_id TEXT, number INTEGER, author_id TEXT, agent_id TEXT, verdict TEXT, created_at TEXT);
19 CREATE TABLE sessions (id TEXT, repo_id TEXT, pull INTEGER, started_at TEXT);
20 CREATE TABLE session_links (session_id TEXT, repo_id TEXT, kind TEXT, number INTEGER);
21 CREATE TABLE session_touches (session_id TEXT, repo_id TEXT, kind TEXT, value TEXT);
22 CREATE TABLE work_items (id TEXT, workspace TEXT, number INTEGER, title TEXT, state TEXT,
23 blocked_by TEXT DEFAULT '[]', milestone INTEGER);
24 CREATE TABLE milestones (workspace TEXT, number INTEGER, title TEXT, state TEXT, due_on TEXT,
25 created_at TEXT, closed_at TEXT);",
26 )
27 .unwrap();
28 db
29}
30
31/// A pull request of repository `a`, by `ana` unless changed afterwards.
32fn pull(db: &Connection, number: u32, status: &str, created: &str, merged: Option<&str>, updated: &str) {
33 db.execute(
34 "INSERT INTO pulls (id, repo_id, number, status, created_at, merged_at, updated_at, source_branch)
35 VALUES (?1, 'a', ?2, ?3, ?4, ?5, ?6, ?7)",
36 params![
37 format!("pr_{number}"),
38 number,
39 status,
40 created,
41 merged,
42 updated,
43 format!("branch-{number}")
44 ],
45 )
46 .unwrap();
47}
48
49fn comment(db: &Connection, number: u32, author: &str, agent: Option<&str>, verdict: Option<&str>, at: &str) {
50 db.execute(
51 "INSERT INTO comments VALUES ('a', ?1, ?2, ?3, ?4, ?5)",
52 params![number, author, agent, verdict, at],
53 )
54 .unwrap();
55}
56
57/// Every row `sql` returns, read as `T` the way D1's rows are.
58fn rows<T: DeserializeOwned>(db: &Connection, sql: &str, binds: &[&dyn ToSql]) -> Vec<T> {
59 let mut statement = db.prepare(sql).unwrap();
60 let names: Vec<String> = statement.column_names().into_iter().map(str::to_owned).collect();
61 let mut found = statement.query(binds).unwrap();
62 let mut out = Vec::new();
63 while let Some(row) = found.next().unwrap() {
64 let mut object = serde_json::Map::new();
65 for (i, name) in names.iter().enumerate() {
66 let value = match row.get_ref(i).unwrap() {
67 ValueRef::Null => serde_json::Value::Null,
68 ValueRef::Integer(n) => n.into(),
69 ValueRef::Real(n) => n.into(),
70 ValueRef::Text(text) => String::from_utf8_lossy(text).into_owned().into(),
71 ValueRef::Blob(_) => panic!("no blobs here"),
72 };
73 object.insert(name.clone(), value);
74 }
75 out.push(serde_json::from_value(object.into()).unwrap());
76 }
77 out
78}
79
80fn args(until: &str, weeks: u32, days: u32, since: &str) -> WorkspaceSignalsArgs {
81 WorkspaceSignalsArgs {
82 workspace: "acme".into(),
83 repo_ids: vec!["a".into()],
84 viewer: None,
85 until: until.into(),
86 weeks,
87 days,
88 since: since.into(),
89 }
90}
91
92fn ms(at: &str) -> u64 {
93 g1t_contracts::time::parse_rfc3339(at).unwrap()
94}
95
96/// Two weeks ending with Sunday 2026-10-11 (Mondays 09-28 and 10-05),
97/// read on `now`.
98fn window(now: &str) -> Window {
99 super::window(&args("2026-10-11", 2, 7, "2026-09-01"), ms(now)).unwrap()
100}
101
102fn weeks(db: &Connection, now: &str) -> Vec<SignalWeek> {
103 rows(db, WEEKS, &[&IDS, &window(now).weeks])
104}
105
106fn in_week(found: &[SignalWeek], start: &str) -> SignalWeek {
107 found.iter().find(|week| week.start == start).cloned().unwrap()
108}
109
110fn pulls(db: &Connection) -> Vec<SignalPull> {
111 rows::<PullRow>(db, PULLS, &[&IDS, &"acme", &"2026-01-01T00:00:00"])
112 .into_iter()
113 .map(Into::into)
114 .collect()
115}
116
117fn waiting(db: &Connection) -> Vec<WaitingPull> {
118 rows::<WaitingRow>(db, WAITING, &[&IDS, &"acme"])
119 .into_iter()
120 .map(Into::into)
121 .collect()
122}
123
124#[test]
125fn checks_the_arguments() {
126 let now = ms("2026-10-18T00:00:00Z");
127 let valid = |a: WorkspaceSignalsArgs| super::window(&a, now).is_some();
128 assert!(valid(args("2026-10-11", 13, 28, "2025-09-06")), "400 days back");
129 assert!(!valid(args("2026-10-11", 13, 28, "2025-09-05")), "401 days back");
130 assert!(!valid(args("2026-10-11", 0, 7, "2026-10-01")));
131 assert!(!valid(args("2026-10-11", 14, 7, "2026-10-01")));
132 assert!(!valid(args("2026-10-11", 1, 0, "2026-10-01")));
133 assert!(!valid(args("2026-10-11", 1, 29, "2026-10-01")));
134 assert!(!valid(args("2026-10-11", 1, 7, "2026-10-12")), "since after until");
135 assert!(valid(args("2026-10-11", 1, 7, "2026-10-11")));
136 assert!(!valid(args("2026-02-30", 1, 7, "2026-02-01")));
137 assert!(!valid(args("2026-10-11", 1, 7, "2026-10-1")));
138 let mut many = args("2026-10-11", 1, 7, "2026-10-01");
139 many.repo_ids = (0..101).map(|n| format!("r{n}")).collect();
140 assert!(!valid(many));
141
142 let window = super::window(&args("2026-10-11", 2, 3, "2026-09-01"), now).unwrap();
143 let weeks: serde_json::Value = serde_json::from_str(&window.weeks).unwrap();
144 assert_eq!(weeks[0]["start"], "2026-09-28");
145 assert_eq!(weeks[1]["start"], "2026-10-05");
146 assert_eq!(weeks[1]["to"], "2026-10-12T00:00:00.000Z");
147 assert_eq!(
148 weeks[1]["cut"], "2026-10-12T00:00:00.000Z",
149 "a week already over ends at its end"
150 );
151 assert_eq!(
152 (
153 window.days_from.as_str(),
154 window.end.as_str(),
155 window.since.as_str(),
156 window.recent_from.as_str()
157 ),
158 (
159 "2026-10-09T00:00:00",
160 "2026-10-12T00:00:00",
161 "2026-09-01T00:00:00",
162 "2026-07-14T00:00:00"
163 )
164 );
165}
166
167#[test]
168fn an_agents_pull_request_is_one_a_session_is_tied_to() {
169 let db = schema();
170 for number in 1..=6 {
171 pull(
172 &db,
173 number,
174 "open",
175 "2026-10-06T12:00:00Z",
176 None,
177 "2026-10-06T12:00:00Z",
178 );
179 }
180 db.execute_batch(
181 "UPDATE pulls SET agent = 'agent' WHERE number = 1;
182 UPDATE pulls SET author_id = 'usr_g1t_agent', requested_by_id = 'usr_ana', requested_by_name = 'ana' WHERE number = 2;
183 -- 3 by a link, 4 by the session saying so, 5 by a push to its branch.
184 INSERT INTO sessions VALUES ('ses_3', 'a', NULL, '2026-10-06T11:00:00Z');
185 INSERT INTO session_links VALUES ('ses_3', 'a', 'pull', 3);
186 INSERT INTO sessions VALUES ('ses_4', 'a', 4, '2026-10-06T13:00:00Z');
187 INSERT INTO sessions VALUES ('ses_5', 'a', NULL, '2026-10-06T11:30:00Z');
188 INSERT INTO sessions VALUES ('ses_5b', 'a', NULL, '2026-10-06T10:00:00.500Z');
189 INSERT INTO session_touches VALUES ('ses_5', 'a', 'branch', 'branch-5');
190 INSERT INTO session_touches VALUES ('ses_5b', 'a', 'branch', 'branch-5');
191 -- 6's branch is on a fork: a push to the same name is not its.
192 UPDATE pulls SET fork_repo_id = 'fork' WHERE number = 6;
193 INSERT INTO sessions VALUES ('ses_6', 'a', NULL, '2026-10-06T11:00:00Z');
194 INSERT INTO session_touches VALUES ('ses_6', 'a', 'branch', 'branch-6');
195 -- A session of another repository with the same number is not its.
196 INSERT INTO sessions VALUES ('ses_x', 'b', 6, '2026-10-06T11:00:00Z');",
197 )
198 .unwrap();
199 pull(&db, 7, "open", "2026-10-06T12:00:00Z", None, "2026-10-06T12:00:00Z");
200 let mut agents: Vec<(u32, bool)> = pulls(&db).iter().map(|p| (p.number, p.agent)).collect();
201 agents.sort();
202 assert_eq!(
203 agents,
204 [
205 (1, true),
206 (2, true),
207 (3, true),
208 (4, true),
209 (5, true),
210 (6, false),
211 (7, false)
212 ]
213 );
214 assert_eq!(
215 pulls(&db).iter().find(|p| p.number == 2).unwrap().owner_id,
216 "usr_ana",
217 "whose g1t's is"
218 );
219
220 let mut drafts: Vec<(u32, Option<f64>)> = waiting(&db).iter().map(|p| (p.number, p.draft_seconds)).collect();
221 drafts.sort_by_key(|(number, _)| *number);
222 // 4's session started after it opened: no draft time. 5's first
223 // session started 2 hours less half a second before.
224 assert!(drafts[2].0 == 3 && (drafts[2].1.unwrap() - 3600.0).abs() < 0.01);
225 assert_eq!(drafts[3], (4, None));
226 assert!(drafts[4].0 == 5 && (drafts[4].1.unwrap() - 7199.5).abs() < 0.01);
227 assert_eq!((drafts[0].1, drafts[5].1), (None, None));
228
229 let week = in_week(&weeks(&db, "2026-10-18T00:00:00Z"), "2026-10-05");
230 assert_eq!(week.agent_drafted, 2);
231 assert!((week.agent_draft_seconds - 10799.5).abs() < 0.01);
232}
233
234#[test]
235fn rework_is_a_persons_request_for_changes_before_it_merged() {
236 let db = schema();
237 pull(
238 &db,
239 1,
240 "merged",
241 "2026-10-05T00:00:00Z",
242 Some("2026-10-07T00:00:00Z"),
243 "2026-10-07T00:00:00Z",
244 );
245 pull(
246 &db,
247 2,
248 "merged",
249 "2026-10-05T00:00:00Z",
250 Some("2026-10-07T00:00:00Z"),
251 "2026-10-07T00:00:00Z",
252 );
253 db.execute_batch("UPDATE pulls SET agent = 'agent'").unwrap();
254 // None of these is a person's, before it merged, on 1.
255 comment(&db, 1, "usr_ana", None, Some("request_changes"), "2026-10-06T00:00:00Z");
256 comment(
257 &db,
258 1,
259 "usr_g1t_agent",
260 None,
261 Some("request_changes"),
262 "2026-10-06T00:00:00Z",
263 );
264 comment(
265 &db,
266 1,
267 "agt_margo",
268 Some("agt_margo"),
269 Some("request_changes"),
270 "2026-10-06T00:00:00Z",
271 );
272 comment(&db, 1, "usr_bo", None, Some("approve"), "2026-10-06T00:00:00Z");
273 comment(&db, 1, "usr_bo", None, Some("request_changes"), "2026-10-08T00:00:00Z");
274 comment(&db, 2, "usr_bo", None, Some("request_changes"), "2026-10-06T00:00:00Z");
275 let found = weeks(&db, "2026-10-18T00:00:00Z");
276 assert_eq!(found.len(), 1);
277 let week = &found[0];
278 assert_eq!(
279 (week.start.as_str(), week.merged, week.agent_merged, week.agent_reworked),
280 ("2026-10-05", 2, 2, 1)
281 );
282 // A person's pull request asked for changes is not an agent's rework.
283 db.execute("UPDATE pulls SET agent = 'person' WHERE number = 2", [])
284 .unwrap();
285 let week = in_week(&weeks(&db, "2026-10-18T00:00:00Z"), "2026-10-05");
286 assert_eq!((week.merged, week.agent_merged, week.agent_reworked), (2, 1, 0));
287}
288
289#[test]
290fn stalled_is_open_over_a_week_when_the_week_ended() {
291 let db = schema();
292 // The second week ends at 2026-10-12T00:00:00Z: over seven days open
293 // by then is opened before 10-05.
294 pull(&db, 1, "open", "2026-10-05T00:00:00Z", None, "2026-10-05T00:00:00Z");
295 pull(&db, 2, "open", "2026-10-04T23:59:59Z", None, "2026-10-04T23:59:59Z");
296 // Merged after the week ended: still open when it did.
297 pull(
298 &db,
299 3,
300 "merged",
301 "2026-09-01T00:00:00Z",
302 Some("2026-10-12T00:00:00Z"),
303 "2026-10-12T00:00:00Z",
304 );
305 // Merged and closed before it ended.
306 pull(
307 &db,
308 4,
309 "merged",
310 "2026-09-01T00:00:00Z",
311 Some("2026-10-11T23:00:00Z"),
312 "2026-10-11T23:00:00Z",
313 );
314 pull(&db, 5, "closed", "2026-09-01T00:00:00Z", None, "2026-10-11T23:00:00Z");
315 // Closed after it ended.
316 pull(&db, 6, "closed", "2026-09-01T00:00:00Z", None, "2026-10-13T00:00:00Z");
317 pull(&db, 7, "draft", "2026-09-27T00:00:00Z", None, "2026-09-27T00:00:00Z");
318 let found = weeks(&db, "2026-10-18T00:00:00Z");
319 let stalled: Vec<(&str, u32)> = found.iter().map(|w| (w.start.as_str(), w.stalled)).collect();
320 // The first week ends on 10-05: 3 to 6 open since 09-01, and 7 opened
321 // a second over seven days before. The second: 2, 3, 6 and 7.
322 assert_eq!(stalled, [("2026-09-28", 5), ("2026-10-05", 4)]);
323
324 // Read mid-week, the current week ends now, 10-08T12:00: 3, 4, 5 and
325 // 6 were not finished by then, 7 is old enough, 2 is not, and 1 at
326 // exactly seven days is not either.
327 db.execute(
328 "UPDATE pulls SET created_at = '2026-10-01T12:00:00Z' WHERE number = 1",
329 [],
330 )
331 .unwrap();
332 assert_eq!(in_week(&weeks(&db, "2026-10-08T12:00:00Z"), "2026-10-05").stalled, 5);
333 db.execute(
334 "UPDATE pulls SET created_at = '2026-10-01T11:59:59Z' WHERE number = 1",
335 [],
336 )
337 .unwrap();
338 assert_eq!(in_week(&weeks(&db, "2026-10-08T12:00:00Z"), "2026-10-05").stalled, 6);
339}
340
341#[test]
342fn a_first_review_is_a_persons_other_than_the_owner() {
343 let db = schema();
344 pull(&db, 1, "open", "2026-10-06T00:00:00Z", None, "2026-10-06T00:00:00Z");
345 db.execute_batch(
346 "UPDATE pulls SET author_id = 'usr_g1t_agent', requested_by_id = 'owner', requested_by_name = 'owner'",
347 )
348 .unwrap();
349 comment(&db, 1, "usr_g1t_agent", None, Some("approve"), "2026-10-06T01:00:00Z");
350 comment(&db, 1, "owner", None, Some("approve"), "2026-10-06T02:00:00Z");
351 comment(
352 &db,
353 1,
354 "agt_margo",
355 Some("agt_margo"),
356 Some("approve"),
357 "2026-10-06T03:00:00Z",
358 );
359 comment(&db, 1, "usr_bo", None, None, "2026-10-06T03:30:00Z");
360 assert!(weeks(&db, "2026-10-18T00:00:00Z").iter().all(|w| w.reviewed == 0));
361 assert_eq!(
362 waiting(&db).len(),
363 1,
364 "g1t, an agent, the owner and a plain comment are no review"
365 );
366 comment(&db, 1, "usr_bo", None, Some("request_changes"), "2026-10-06T04:00:00Z");
367 comment(&db, 1, "usr_cy", None, Some("approve"), "2026-10-06T05:00:00Z");
368 let week = in_week(&weeks(&db, "2026-10-18T00:00:00Z"), "2026-10-05");
369 assert_eq!(week.reviewed, 1);
370 assert!((week.review_seconds - 4.0 * 3600.0).abs() < 1.0);
371 assert!(waiting(&db).is_empty());
372}
373
374#[test]
375fn waiting_pull_requests_name_the_work_they_block() {
376 let db = schema();
377 pull(&db, 10, "open", "2026-10-02T00:00:00Z", None, "2026-10-02T00:00:00Z");
378 pull(&db, 11, "open", "2026-10-01T00:00:00Z", None, "2026-10-01T00:00:00Z");
379 pull(&db, 12, "draft", "2026-09-01T00:00:00Z", None, "2026-09-01T00:00:00Z");
380 db.execute_batch(
381 "UPDATE pulls SET item_id = 'wki_1' WHERE number = 10;
382 INSERT INTO work_items VALUES ('wki_1', 'acme', 1, 'Login', 'open', '[]', 3);
383 INSERT INTO work_items VALUES ('wki_2', 'acme', 2, 'Signup', 'open', '[\"wki_9\", \"wki_1\"]', NULL);
384 INSERT INTO work_items VALUES ('wki_4', 'acme', 4, 'Billing', 'open', '[\"wki_1\"]', NULL);
385 INSERT INTO work_items VALUES ('wki_5', 'acme', 5, 'Done', 'closed', '[\"wki_1\"]', NULL);
386 INSERT INTO work_items VALUES ('wki_6', 'other', 6, 'Elsewhere', 'open', '[\"wki_1\"]', NULL);
387 INSERT INTO work_items VALUES ('wki_7', 'acme', 7, 'Unrelated', 'open', '[\"wki_2\"]', NULL);",
388 )
389 .unwrap();
390 let found = waiting(&db);
391 let shown: Vec<(u32, Option<u32>, Vec<u32>)> = found.iter().map(|p| (p.number, p.item, p.blocks.clone())).collect();
392 assert_eq!(
393 shown,
394 [(11, None, vec![]), (10, Some(1), vec![2, 4])],
395 "oldest first; drafts wait on no one"
396 );
397 // Its milestone is its work item's.
398 let pull = pulls(&db).into_iter().find(|p| p.number == 10).unwrap();
399 assert_eq!((pull.item, pull.milestone), (Some(1), Some(3)));
400}
401
402#[test]
403fn a_pull_requests_own_milestone_counts_only_when_it_is_the_workspaces() {
404 let db = schema();
405 pull(&db, 1, "open", "2026-10-02T00:00:00Z", None, "2026-10-02T00:00:00Z");
406 pull(&db, 2, "open", "2026-10-02T00:00:00Z", None, "2026-10-02T00:00:00Z");
407 pull(
408 &db,
409 3,
410 "merged",
411 "2025-01-01T00:00:00Z",
412 Some("2026-10-03T00:00:00Z"),
413 "2026-10-03T00:00:00Z",
414 );
415 pull(&db, 4, "closed", "2025-01-01T00:00:00Z", None, "2026-10-03T00:00:00Z");
416 db.execute_batch(
417 "UPDATE pulls SET milestone = 1 WHERE number = 1;
418 UPDATE pulls SET milestone = 8 WHERE number = 2;
419 INSERT INTO milestones VALUES ('acme', 1, 'Launch', 'open', NULL, '2026-01-01T00:00:00Z', NULL);
420 INSERT INTO milestones VALUES ('other', 8, 'Not ours', 'open', NULL, '2026-01-01T00:00:00Z', NULL);",
421 )
422 .unwrap();
423 let mut found: Vec<(u32, Option<u32>)> = pulls(&db).iter().map(|p| (p.number, p.milestone)).collect();
424 found.sort();
425 assert_eq!(
426 found,
427 [(1, Some(1)), (2, None), (3, None)],
428 "merged since counts; closed long-opened does not"
429 );
430}
431
432#[test]
433fn authors_are_people_with_merged_work() {
434 let db = schema();
435 pull(
436 &db,
437 1,
438 "merged",
439 "2025-01-01T00:00:00Z",
440 Some("2025-01-02T00:00:00Z"),
441 "2025-01-02T00:00:00Z",
442 );
443 pull(
444 &db,
445 2,
446 "merged",
447 "2026-10-01T00:00:00Z",
448 Some("2026-10-02T00:00:00Z"),
449 "2026-10-02T00:00:00Z",
450 );
451 pull(&db, 3, "open", "2026-10-01T00:00:00Z", None, "2026-10-01T00:00:00Z");
452 for (number, author) in [
453 (4, "usr_g1t_agent"),
454 (5, "agt_margo"),
455 (6, "wsp_acme"),
456 (7, "g1t"),
457 (8, "svc_runner"),
458 ] {
459 pull(
460 &db,
461 number,
462 "merged",
463 "2026-10-01T00:00:00Z",
464 Some("2026-10-02T00:00:00Z"),
465 "2026-10-02T00:00:00Z",
466 );
467 db.execute(
468 "UPDATE pulls SET author_id = ?1 WHERE number = ?2",
469 params![author, number],
470 )
471 .unwrap();
472 }
473 // g1t's agent's work for a person is theirs, under their name now.
474 pull(
475 &db,
476 9,
477 "merged",
478 "2026-10-01T00:00:00Z",
479 Some("2026-10-03T00:00:00Z"),
480 "2026-10-03T00:00:00Z",
481 );
482 db.execute_batch(
483 "UPDATE pulls SET author_id = 'usr_g1t_agent', requested_by_id = 'usr_ana', requested_by_name = 'ana-renamed' WHERE number = 9",
484 )
485 .unwrap();
486 let found: Vec<SignalAuthor> =
487 rows::<SignalAuthor>(&db, AUTHORS, &[&IDS, &"2026-07-14T00:00:00", &"2026-10-12T00:00:00"])
488 .into_iter()
489 .filter(|author| is_person(&author.owner_id))
490 .collect();
491 assert_eq!(found.len(), 1);
492 let ana = &found[0];
493 assert_eq!(
494 (
495 ana.owner_id.as_str(),
496 ana.owner_name.as_str(),
497 ana.first_merged_at.as_str(),
498 ana.last_merged_at.as_str()
499 ),
500 ("usr_ana", "ana-renamed", "2025-01-02T00:00:00Z", "2026-10-03T00:00:00Z")
501 );
502 assert_eq!((ana.merged, ana.recent), (3, 2));
503}
504
505#[test]
506fn days_count_what_happened_on_each() {
507 let db = schema();
508 pull(
509 &db,
510 1,
511 "merged",
512 "2026-10-09T10:00:00Z",
513 Some("2026-10-10T10:00:00Z"),
514 "2026-10-10T10:00:00Z",
515 );
516 pull(&db, 2, "open", "2026-10-08T10:00:00Z", None, "2026-10-08T10:00:00Z");
517 db.execute_batch(
518 "UPDATE pulls SET agent = 'agent' WHERE number = 1;
519 INSERT INTO sessions VALUES ('ses_1', 'a', NULL, '2026-10-10T09:00:00Z');
520 INSERT INTO sessions VALUES ('ses_2', 'b', NULL, '2026-10-10T09:00:00Z');",
521 )
522 .unwrap();
523 comment(&db, 1, "usr_bo", None, Some("request_changes"), "2026-10-10T01:00:00Z");
524 comment(&db, 1, "usr_bo", None, None, "2026-10-10T02:00:00Z");
525 comment(&db, 1, "usr_ana", None, None, "2026-10-10T03:00:00Z");
526 comment(&db, 1, "g1t", None, None, "2026-10-10T03:00:00Z");
527 comment(&db, 1, "agt_margo", Some("agt_margo"), None, "2026-10-10T03:00:00Z");
528 let window = window("2026-10-18T00:00:00Z");
529 let days: Vec<SignalDay> = rows(&db, DAYS, &[&IDS, &window.days_from, &window.end]);
530 let shown: Vec<(&str, u32, u32, u32, u32, u32, u32)> = days
531 .iter()
532 .map(|d| {
533 (
534 d.day.as_str(),
535 d.opened,
536 d.merged,
537 d.agent_merged,
538 d.reworked,
539 d.reviews,
540 d.sessions,
541 )
542 })
543 .collect();
544 assert_eq!(
545 shown,
546 [
547 ("2026-10-08", 1, 0, 0, 0, 0, 0),
548 ("2026-10-09", 1, 0, 0, 0, 0, 0),
549 ("2026-10-10", 0, 1, 1, 1, 2, 1)
550 ],
551 "the owner, g1t and an agent are not reviewers; another repository's session is not counted"
552 );
553}
554
555#[test]
556fn days_leave_out_g1t_as_its_system_ids_say() {
557 let mut ids = vec![g1t_contracts::system::ID];
558 ids.extend(g1t_contracts::system::LEGACY_IDS);
559 for id in ids {
560 assert!(DAYS.contains(&format!("'{id}'")), "{id}");
561 assert!(!is_person(id));
562 }
563 assert!(is_person("usr_ana"));
564}