阶段4验证埋点持久化与后台读取
补充 tracking draft、runtime input 和 outbox round-trip 验证 覆盖 SpacetimeDB mapper 与 metadata object 校验 补充账号态和 External API Key 后台 tracking readback 回归
This commit is contained in:
@@ -4394,7 +4394,7 @@ mod tests {
|
||||
http::{Method, StatusCode},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use serde_json::json;
|
||||
use serde_json::{Value, json};
|
||||
use shared_contracts::admin::{
|
||||
AdminConfirmEditorShowcaseCampaignImageUploadRequest, AdminDashboardQuery,
|
||||
AdminDatabaseTableRowsQuery, AdminTrackingEventListQuery,
|
||||
@@ -4407,7 +4407,11 @@ mod tests {
|
||||
SpacetimeClientStage,
|
||||
};
|
||||
|
||||
use crate::{config::AppConfig, state::AppState};
|
||||
use crate::{
|
||||
config::AppConfig,
|
||||
state::AppState,
|
||||
tracking::{TrackingEventDraft, build_tracking_event_input},
|
||||
};
|
||||
|
||||
fn test_admin_editor_showcase_asset_record(owner_user_id: &str) -> EditorShowcaseAssetRecord {
|
||||
EditorShowcaseAssetRecord {
|
||||
@@ -5976,6 +5980,161 @@ mod tests {
|
||||
assert_eq!(entries[0].module_key.as_deref(), Some("profile"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_admin_tracking_events_sql_response_preserves_agc_metadata_and_owner() {
|
||||
let payload = json!([
|
||||
{
|
||||
"rows": [
|
||||
[
|
||||
"account-event-225",
|
||||
"editor_projects_view",
|
||||
"user",
|
||||
"user-225",
|
||||
20600,
|
||||
{"some": "user-225"},
|
||||
{"some": "user-225"},
|
||||
null,
|
||||
"editor",
|
||||
"{\"route\":\"/api/editor/projects\",\"method\":\"GET\",\"status\":200,\"operation\":\"listEditorProjects\",\"client\":\"agc\"}",
|
||||
"2026-09-02T00:00:00Z"
|
||||
],
|
||||
[
|
||||
"external-event-225",
|
||||
"editor_image_generation_submit",
|
||||
"user",
|
||||
"owner-225",
|
||||
20600,
|
||||
null,
|
||||
{"some": "owner-225"},
|
||||
null,
|
||||
"editor",
|
||||
"{\"route\":\"/api/external/v1/editor/images/generations\",\"method\":\"POST\",\"status\":202,\"operation\":\"generateExternalEditorImage\",\"client\":\"agc\"}",
|
||||
"2026-09-02T00:00:01Z"
|
||||
]
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
let entries = parse_admin_tracking_events_sql_response(payload)
|
||||
.expect("AGC tracking rows should be readable by the admin parser");
|
||||
|
||||
assert_eq!(entries.len(), 2);
|
||||
let account = entries
|
||||
.iter()
|
||||
.find(|entry| entry.event_id == "account-event-225")
|
||||
.expect("account event should be present");
|
||||
let account_metadata = serde_json::from_str::<serde_json::Value>(&account.metadata_json)
|
||||
.expect("account metadata should remain valid JSON");
|
||||
assert_eq!(account_metadata["client"], "agc");
|
||||
assert_eq!(account_metadata["route"], "/api/editor/projects");
|
||||
assert_eq!(account.user_id.as_deref(), Some("user-225"));
|
||||
assert_eq!(account.owner_user_id.as_deref(), Some("user-225"));
|
||||
|
||||
let external = entries
|
||||
.iter()
|
||||
.find(|entry| entry.event_id == "external-event-225")
|
||||
.expect("External API Key event should be present");
|
||||
let external_metadata = serde_json::from_str::<serde_json::Value>(&external.metadata_json)
|
||||
.expect("External API Key metadata should remain valid JSON");
|
||||
assert_eq!(external_metadata["client"], "agc");
|
||||
assert_eq!(
|
||||
external_metadata["route"],
|
||||
"/api/external/v1/editor/images/generations"
|
||||
);
|
||||
assert_eq!(external.user_id, None);
|
||||
assert_eq!(external.owner_user_id.as_deref(), Some("owner-225"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tracking_inputs_round_trip_to_admin_readback_for_both_subjects() {
|
||||
let mut account_draft = TrackingEventDraft::new("editor_projects_view", "editor");
|
||||
account_draft.scope_kind = module_runtime::RuntimeTrackingScopeKind::User;
|
||||
account_draft.scope_id = "user-225".to_string();
|
||||
account_draft.user_id = Some("user-225".to_string());
|
||||
account_draft.owner_user_id = Some("user-225".to_string());
|
||||
account_draft.metadata = json!({
|
||||
"route": "/api/editor/projects",
|
||||
"method": "GET",
|
||||
"status": 200,
|
||||
"operation": "listEditorProjects",
|
||||
"client": "agc",
|
||||
});
|
||||
|
||||
let mut external_draft =
|
||||
TrackingEventDraft::new("editor_image_generation_submit", "editor");
|
||||
external_draft.scope_kind = module_runtime::RuntimeTrackingScopeKind::User;
|
||||
external_draft.scope_id = "owner-225".to_string();
|
||||
external_draft.owner_user_id = Some("owner-225".to_string());
|
||||
external_draft.metadata = json!({
|
||||
"route": "/api/external/v1/editor/images/generations",
|
||||
"method": "POST",
|
||||
"status": 202,
|
||||
"operation": "generateExternalEditorImage",
|
||||
"client": "agc",
|
||||
});
|
||||
|
||||
let inputs = [
|
||||
build_tracking_event_input(account_draft),
|
||||
build_tracking_event_input(external_draft),
|
||||
];
|
||||
let row_for_input = |input: &module_runtime::RuntimeTrackingEventInput,
|
||||
occurred_at: &str| {
|
||||
let optional_column = |value: &Option<String>| {
|
||||
value
|
||||
.as_ref()
|
||||
.map(|value| json!({"some": value}))
|
||||
.unwrap_or(Value::Null)
|
||||
};
|
||||
json!([
|
||||
input.event_id.clone(),
|
||||
input.event_key.clone(),
|
||||
"user",
|
||||
input.scope_id.clone(),
|
||||
20600,
|
||||
optional_column(&input.user_id),
|
||||
optional_column(&input.owner_user_id),
|
||||
optional_column(&input.profile_id),
|
||||
optional_column(&input.module_key),
|
||||
input.metadata_json.clone(),
|
||||
occurred_at,
|
||||
])
|
||||
};
|
||||
let payload = json!([{
|
||||
"rows": [
|
||||
row_for_input(&inputs[0], "2026-09-02T00:00:00Z"),
|
||||
row_for_input(&inputs[1], "2026-09-02T00:00:01Z"),
|
||||
]
|
||||
}]);
|
||||
|
||||
let entries = parse_admin_tracking_events_sql_response(payload)
|
||||
.expect("tracking inputs should remain readable by admin readback");
|
||||
assert_eq!(entries.len(), 2);
|
||||
|
||||
let account = entries
|
||||
.iter()
|
||||
.find(|entry| entry.event_key == "editor_projects_view")
|
||||
.expect("account input should be readable");
|
||||
let account_metadata = serde_json::from_str::<Value>(&account.metadata_json)
|
||||
.expect("account metadata should remain valid JSON");
|
||||
assert_eq!(account_metadata["client"], "agc");
|
||||
assert_eq!(account.user_id.as_deref(), Some("user-225"));
|
||||
assert_eq!(account.owner_user_id.as_deref(), Some("user-225"));
|
||||
|
||||
let external = entries
|
||||
.iter()
|
||||
.find(|entry| entry.event_key == "editor_image_generation_submit")
|
||||
.expect("External API Key input should be readable");
|
||||
let external_metadata = serde_json::from_str::<Value>(&external.metadata_json)
|
||||
.expect("External API Key metadata should remain valid JSON");
|
||||
assert_eq!(external_metadata["client"], "agc");
|
||||
assert_eq!(
|
||||
external_metadata["route"],
|
||||
"/api/external/v1/editor/images/generations"
|
||||
);
|
||||
assert_eq!(external.user_id, None);
|
||||
assert_eq!(external.owner_user_id.as_deref(), Some("owner-225"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_admin_tracking_events_sql_response_normalizes_sats_values() {
|
||||
let payload = json!([
|
||||
|
||||
Reference in New Issue
Block a user