parent
91459c16cc
commit
0a2e7daec9
10 changed files with 202 additions and 40 deletions
|
|
@ -4,7 +4,7 @@ CREATE TABLE IF NOT EXISTS projects (
|
|||
path TEXT NOT NULL,
|
||||
cloned_from TEXT,
|
||||
base_branch TEXT NOT NULL DEFAULT 'main',
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tuleap_credentials (
|
||||
|
|
@ -22,7 +22,7 @@ CREATE TABLE IF NOT EXISTS watched_trackers (
|
|||
polling_interval INTEGER NOT NULL DEFAULT 10,
|
||||
agent_config_json TEXT NOT NULL,
|
||||
filters_json TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS processed_tickets (
|
||||
|
|
@ -36,7 +36,7 @@ CREATE TABLE IF NOT EXISTS processed_tickets (
|
|||
developer_report TEXT,
|
||||
worktree_path TEXT,
|
||||
branch_name TEXT,
|
||||
detected_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
detected_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||
processed_at TEXT
|
||||
);
|
||||
|
||||
|
|
@ -49,7 +49,7 @@ CREATE TABLE IF NOT EXISTS worktrees (
|
|||
path TEXT NOT NULL,
|
||||
branch_name TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'Active',
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||
merged_at TEXT,
|
||||
merged_into TEXT
|
||||
);
|
||||
|
|
@ -62,5 +62,5 @@ CREATE TABLE IF NOT EXISTS notifications (
|
|||
title TEXT NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
read INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ CREATE TABLE IF NOT EXISTS agents (
|
|||
role TEXT NOT NULL,
|
||||
tool TEXT NOT NULL,
|
||||
custom_prompt TEXT NOT NULL DEFAULT '',
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
||||
);
|
||||
|
||||
ALTER TABLE watched_trackers ADD COLUMN analyst_agent_id TEXT;
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ SELECT
|
|||
'codex',
|
||||
'',
|
||||
1,
|
||||
datetime('now'),
|
||||
datetime('now')
|
||||
strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
|
||||
strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM agents WHERE role = 'analyst' AND is_default = 1
|
||||
);
|
||||
|
|
@ -26,8 +26,8 @@ SELECT
|
|||
'claude_code',
|
||||
'',
|
||||
1,
|
||||
datetime('now'),
|
||||
datetime('now')
|
||||
strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
|
||||
strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM agents WHERE role = 'developer' AND is_default = 1
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ CREATE TABLE IF NOT EXISTS project_modules (
|
|||
description TEXT NOT NULL DEFAULT '',
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
config_json TEXT NOT NULL DEFAULT '{}',
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||
UNIQUE(project_id, module_key)
|
||||
);
|
||||
|
||||
|
|
@ -19,8 +19,8 @@ CREATE TABLE IF NOT EXISTS project_live_sessions (
|
|||
agent_id TEXT NOT NULL REFERENCES agents(id),
|
||||
title TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_live_sessions_project_id ON project_live_sessions(project_id);
|
||||
|
|
@ -30,7 +30,7 @@ CREATE TABLE IF NOT EXISTS project_live_messages (
|
|||
session_id TEXT NOT NULL REFERENCES project_live_sessions(id) ON DELETE CASCADE,
|
||||
sender TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_live_messages_session_id ON project_live_messages(session_id);
|
||||
|
|
@ -44,7 +44,7 @@ CREATE TABLE IF NOT EXISTS project_agent_tasks (
|
|||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
result TEXT,
|
||||
error TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||
started_at TEXT,
|
||||
finished_at TEXT
|
||||
);
|
||||
|
|
@ -71,8 +71,8 @@ SELECT
|
|||
'Surveille Tuleap et lance le pipeline analyste/developpeur.',
|
||||
1,
|
||||
'{}',
|
||||
datetime('now'),
|
||||
datetime('now')
|
||||
strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
|
||||
strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
||||
FROM projects p;
|
||||
|
||||
INSERT OR IGNORE INTO project_modules (
|
||||
|
|
@ -94,8 +94,8 @@ SELECT
|
|||
'Discussion live avec un agent sur le contexte du projet.',
|
||||
1,
|
||||
'{}',
|
||||
datetime('now'),
|
||||
datetime('now')
|
||||
strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
|
||||
strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
||||
FROM projects p;
|
||||
|
||||
INSERT OR IGNORE INTO project_modules (
|
||||
|
|
@ -117,6 +117,6 @@ SELECT
|
|||
'File de tâches asynchrones traitées par des agents pré-définis.',
|
||||
1,
|
||||
'{}',
|
||||
datetime('now'),
|
||||
datetime('now')
|
||||
strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
|
||||
strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
||||
FROM projects p;
|
||||
|
|
|
|||
93
src-tauri/migrations/007_normalize_timestamps_rfc3339.sql
Normal file
93
src-tauri/migrations/007_normalize_timestamps_rfc3339.sql
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
BEGIN;
|
||||
|
||||
UPDATE projects
|
||||
SET created_at = strftime('%Y-%m-%dT%H:%M:%fZ', created_at)
|
||||
WHERE created_at IS NOT NULL
|
||||
AND strftime('%Y-%m-%dT%H:%M:%fZ', created_at) IS NOT NULL;
|
||||
|
||||
UPDATE watched_trackers
|
||||
SET created_at = strftime('%Y-%m-%dT%H:%M:%fZ', created_at)
|
||||
WHERE created_at IS NOT NULL
|
||||
AND strftime('%Y-%m-%dT%H:%M:%fZ', created_at) IS NOT NULL;
|
||||
|
||||
UPDATE watched_trackers
|
||||
SET last_polled_at = strftime('%Y-%m-%dT%H:%M:%fZ', last_polled_at)
|
||||
WHERE last_polled_at IS NOT NULL
|
||||
AND strftime('%Y-%m-%dT%H:%M:%fZ', last_polled_at) IS NOT NULL;
|
||||
|
||||
UPDATE processed_tickets
|
||||
SET detected_at = strftime('%Y-%m-%dT%H:%M:%fZ', detected_at)
|
||||
WHERE detected_at IS NOT NULL
|
||||
AND strftime('%Y-%m-%dT%H:%M:%fZ', detected_at) IS NOT NULL;
|
||||
|
||||
UPDATE processed_tickets
|
||||
SET processed_at = strftime('%Y-%m-%dT%H:%M:%fZ', processed_at)
|
||||
WHERE processed_at IS NOT NULL
|
||||
AND strftime('%Y-%m-%dT%H:%M:%fZ', processed_at) IS NOT NULL;
|
||||
|
||||
UPDATE worktrees
|
||||
SET created_at = strftime('%Y-%m-%dT%H:%M:%fZ', created_at)
|
||||
WHERE created_at IS NOT NULL
|
||||
AND strftime('%Y-%m-%dT%H:%M:%fZ', created_at) IS NOT NULL;
|
||||
|
||||
UPDATE worktrees
|
||||
SET merged_at = strftime('%Y-%m-%dT%H:%M:%fZ', merged_at)
|
||||
WHERE merged_at IS NOT NULL
|
||||
AND strftime('%Y-%m-%dT%H:%M:%fZ', merged_at) IS NOT NULL;
|
||||
|
||||
UPDATE notifications
|
||||
SET created_at = strftime('%Y-%m-%dT%H:%M:%fZ', created_at)
|
||||
WHERE created_at IS NOT NULL
|
||||
AND strftime('%Y-%m-%dT%H:%M:%fZ', created_at) IS NOT NULL;
|
||||
|
||||
UPDATE agents
|
||||
SET created_at = strftime('%Y-%m-%dT%H:%M:%fZ', created_at)
|
||||
WHERE created_at IS NOT NULL
|
||||
AND strftime('%Y-%m-%dT%H:%M:%fZ', created_at) IS NOT NULL;
|
||||
|
||||
UPDATE agents
|
||||
SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', updated_at)
|
||||
WHERE updated_at IS NOT NULL
|
||||
AND strftime('%Y-%m-%dT%H:%M:%fZ', updated_at) IS NOT NULL;
|
||||
|
||||
UPDATE project_modules
|
||||
SET created_at = strftime('%Y-%m-%dT%H:%M:%fZ', created_at)
|
||||
WHERE created_at IS NOT NULL
|
||||
AND strftime('%Y-%m-%dT%H:%M:%fZ', created_at) IS NOT NULL;
|
||||
|
||||
UPDATE project_modules
|
||||
SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', updated_at)
|
||||
WHERE updated_at IS NOT NULL
|
||||
AND strftime('%Y-%m-%dT%H:%M:%fZ', updated_at) IS NOT NULL;
|
||||
|
||||
UPDATE project_live_sessions
|
||||
SET created_at = strftime('%Y-%m-%dT%H:%M:%fZ', created_at)
|
||||
WHERE created_at IS NOT NULL
|
||||
AND strftime('%Y-%m-%dT%H:%M:%fZ', created_at) IS NOT NULL;
|
||||
|
||||
UPDATE project_live_sessions
|
||||
SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', updated_at)
|
||||
WHERE updated_at IS NOT NULL
|
||||
AND strftime('%Y-%m-%dT%H:%M:%fZ', updated_at) IS NOT NULL;
|
||||
|
||||
UPDATE project_live_messages
|
||||
SET created_at = strftime('%Y-%m-%dT%H:%M:%fZ', created_at)
|
||||
WHERE created_at IS NOT NULL
|
||||
AND strftime('%Y-%m-%dT%H:%M:%fZ', created_at) IS NOT NULL;
|
||||
|
||||
UPDATE project_agent_tasks
|
||||
SET created_at = strftime('%Y-%m-%dT%H:%M:%fZ', created_at)
|
||||
WHERE created_at IS NOT NULL
|
||||
AND strftime('%Y-%m-%dT%H:%M:%fZ', created_at) IS NOT NULL;
|
||||
|
||||
UPDATE project_agent_tasks
|
||||
SET started_at = strftime('%Y-%m-%dT%H:%M:%fZ', started_at)
|
||||
WHERE started_at IS NOT NULL
|
||||
AND strftime('%Y-%m-%dT%H:%M:%fZ', started_at) IS NOT NULL;
|
||||
|
||||
UPDATE project_agent_tasks
|
||||
SET finished_at = strftime('%Y-%m-%dT%H:%M:%fZ', finished_at)
|
||||
WHERE finished_at IS NOT NULL
|
||||
AND strftime('%Y-%m-%dT%H:%M:%fZ', finished_at) IS NOT NULL;
|
||||
|
||||
COMMIT;
|
||||
|
|
@ -7,6 +7,7 @@ const MIGRATION_003: &str = include_str!("../migrations/003_add_agents.sql");
|
|||
const MIGRATION_004: &str = include_str!("../migrations/004_default_agents.sql");
|
||||
const MIGRATION_005: &str = include_str!("../migrations/005_orchestration_modules_chat_tasks.sql");
|
||||
const MIGRATION_006: &str = include_str!("../migrations/006_processed_tickets_unique_index.sql");
|
||||
const MIGRATION_007: &str = include_str!("../migrations/007_normalize_timestamps_rfc3339.sql");
|
||||
|
||||
pub fn init(db_path: &Path) -> Result<Connection> {
|
||||
let conn = Connection::open(db_path)?;
|
||||
|
|
@ -56,6 +57,10 @@ fn migrate(conn: &Connection) -> Result<()> {
|
|||
conn.execute_batch(MIGRATION_006)?;
|
||||
conn.pragma_update(None, "user_version", 6)?;
|
||||
}
|
||||
if version < 7 {
|
||||
conn.execute_batch(MIGRATION_007)?;
|
||||
conn.pragma_update(None, "user_version", 7)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -111,7 +116,7 @@ mod tests {
|
|||
let version: i32 = conn
|
||||
.pragma_query_value(None, "user_version", |row| row.get(0))
|
||||
.unwrap();
|
||||
assert_eq!(version, 6);
|
||||
assert_eq!(version, 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -146,9 +146,10 @@ impl ProcessedTicket {
|
|||
}
|
||||
|
||||
pub fn set_developer_report(conn: &Connection, id: &str, report: &str) -> Result<()> {
|
||||
let now = chrono::Utc::now().to_rfc3339();
|
||||
conn.execute(
|
||||
"UPDATE processed_tickets SET developer_report = ?1, processed_at = datetime('now') WHERE id = ?2",
|
||||
params![report, id],
|
||||
"UPDATE processed_tickets SET developer_report = ?1, processed_at = ?2 WHERE id = ?3",
|
||||
params![report, now, id],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -177,9 +178,10 @@ impl ProcessedTicket {
|
|||
}
|
||||
|
||||
pub fn set_error(conn: &Connection, id: &str, error_message: &str) -> Result<()> {
|
||||
let now = chrono::Utc::now().to_rfc3339();
|
||||
conn.execute(
|
||||
"UPDATE processed_tickets SET status = 'Error', analyst_report = COALESCE(analyst_report, '') || ?1, processed_at = datetime('now') WHERE id = ?2",
|
||||
params![error_message, id],
|
||||
"UPDATE processed_tickets SET status = 'Error', analyst_report = COALESCE(analyst_report, '') || ?1, processed_at = ?2 WHERE id = ?3",
|
||||
params![error_message, now, id],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -378,8 +380,15 @@ mod tests {
|
|||
|
||||
ProcessedTicket::set_developer_report(&conn, &ticket.id, "Fixed in main.rs").unwrap();
|
||||
let updated = ProcessedTicket::get_by_id(&conn, &ticket.id).unwrap();
|
||||
assert_eq!(updated.developer_report.unwrap(), "Fixed in main.rs");
|
||||
assert!(updated.processed_at.is_some());
|
||||
assert_eq!(
|
||||
updated.developer_report.as_deref(),
|
||||
Some("Fixed in main.rs")
|
||||
);
|
||||
let processed_at = updated
|
||||
.processed_at
|
||||
.as_deref()
|
||||
.expect("processed_at should be set");
|
||||
assert!(chrono::DateTime::parse_from_rfc3339(processed_at).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -422,6 +431,14 @@ mod tests {
|
|||
ProcessedTicket::set_error(&conn, &ticket.id, "CLI timeout after 600s").unwrap();
|
||||
let updated = ProcessedTicket::get_by_id(&conn, &ticket.id).unwrap();
|
||||
assert_eq!(updated.status, "Error");
|
||||
assert_eq!(updated.analyst_report.unwrap(), "CLI timeout after 600s");
|
||||
assert_eq!(
|
||||
updated.analyst_report.as_deref(),
|
||||
Some("CLI timeout after 600s")
|
||||
);
|
||||
let processed_at = updated
|
||||
.processed_at
|
||||
.as_deref()
|
||||
.expect("processed_at should be set");
|
||||
assert!(chrono::DateTime::parse_from_rfc3339(processed_at).is_ok());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -516,7 +516,11 @@ mod tests {
|
|||
.expect("update_last_polled should succeed");
|
||||
|
||||
let updated = WatchedTracker::get_by_id(&conn, &created.id).unwrap();
|
||||
assert!(updated.last_polled_at.is_some());
|
||||
let last_polled_at = updated
|
||||
.last_polled_at
|
||||
.as_deref()
|
||||
.expect("last_polled_at should be set");
|
||||
assert!(chrono::DateTime::parse_from_rfc3339(last_polled_at).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -88,9 +88,10 @@ impl Worktree {
|
|||
}
|
||||
|
||||
pub fn set_merged(conn: &Connection, id: &str, target_branch: &str) -> Result<()> {
|
||||
let now = chrono::Utc::now().to_rfc3339();
|
||||
conn.execute(
|
||||
"UPDATE worktrees SET status = 'Merged', merged_at = datetime('now'), merged_into = ?1 WHERE id = ?2",
|
||||
params![target_branch, id],
|
||||
"UPDATE worktrees SET status = 'Merged', merged_at = ?1, merged_into = ?2 WHERE id = ?3",
|
||||
params![now, target_branch, id],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -206,7 +207,11 @@ mod tests {
|
|||
let updated = Worktree::get_by_id(&conn, &wt.id).unwrap();
|
||||
assert_eq!(updated.status, "Merged");
|
||||
assert_eq!(updated.merged_into.unwrap(), "feature/login");
|
||||
assert!(updated.merged_at.is_some());
|
||||
let merged_at = updated
|
||||
.merged_at
|
||||
.as_deref()
|
||||
.expect("merged_at should be set");
|
||||
assert!(chrono::DateTime::parse_from_rfc3339(merged_at).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -84,12 +84,12 @@ fn should_poll(tracker: &WatchedTracker) -> bool {
|
|||
Some(s) => s,
|
||||
};
|
||||
|
||||
let last = match chrono::DateTime::parse_from_rfc3339(last_polled_at) {
|
||||
Ok(dt) => dt,
|
||||
Err(e) => {
|
||||
let last = match parse_timestamp(last_polled_at) {
|
||||
Some(dt) => dt,
|
||||
None => {
|
||||
eprintln!(
|
||||
"poller: failed to parse last_polled_at '{}': {}",
|
||||
last_polled_at, e
|
||||
"poller: failed to parse last_polled_at '{}': unsupported format",
|
||||
last_polled_at
|
||||
);
|
||||
return true; // Treat as never polled on parse error
|
||||
}
|
||||
|
|
@ -99,6 +99,21 @@ fn should_poll(tracker: &WatchedTracker) -> bool {
|
|||
elapsed >= tracker.polling_interval as i64
|
||||
}
|
||||
|
||||
fn parse_timestamp(value: &str) -> Option<chrono::DateTime<chrono::Utc>> {
|
||||
if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(value) {
|
||||
return Some(dt.with_timezone(&chrono::Utc));
|
||||
}
|
||||
|
||||
if let Ok(naive) = chrono::NaiveDateTime::parse_from_str(value, "%Y-%m-%d %H:%M:%S%.f") {
|
||||
return Some(chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset(
|
||||
naive,
|
||||
chrono::Utc,
|
||||
));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
async fn poll_single_tracker(
|
||||
db: &Arc<Mutex<Connection>>,
|
||||
client: &TuleapClient,
|
||||
|
|
@ -230,3 +245,26 @@ async fn poll_single_tracker(
|
|||
}),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::parse_timestamp;
|
||||
|
||||
#[test]
|
||||
fn parse_timestamp_supports_rfc3339() {
|
||||
let parsed = parse_timestamp("2026-04-16T10:15:30.123Z");
|
||||
assert!(parsed.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_timestamp_supports_legacy_sqlite_datetime() {
|
||||
let parsed = parse_timestamp("2026-04-16 10:15:30");
|
||||
assert!(parsed.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_timestamp_rejects_invalid_values() {
|
||||
let parsed = parse_timestamp("not-a-date");
|
||||
assert!(parsed.is_none());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue