mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
This commit is contained in:
@@ -44,13 +44,15 @@ describe('memorySyncService.memorySyncStatusList', () => {
|
||||
await expect(memorySyncStatusList()).rejects.toThrow('rpc boom');
|
||||
});
|
||||
|
||||
it('throws on malformed response (missing statuses[])', async () => {
|
||||
it('returns empty array on malformed response (missing statuses[])', async () => {
|
||||
mockCallCoreRpc.mockResolvedValueOnce({ wrong: 'shape' });
|
||||
await expect(memorySyncStatusList()).rejects.toThrow(/missing statuses/);
|
||||
const out = await memorySyncStatusList();
|
||||
expect(out).toEqual([]);
|
||||
});
|
||||
|
||||
it('throws on null response', async () => {
|
||||
it('returns empty array on null response', async () => {
|
||||
mockCallCoreRpc.mockResolvedValueOnce(null);
|
||||
await expect(memorySyncStatusList()).rejects.toThrow(/missing statuses/);
|
||||
const out = await memorySyncStatusList();
|
||||
expect(out).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -57,8 +57,11 @@ export async function memorySyncStatusList(): Promise<MemorySyncStatus[]> {
|
||||
throw err;
|
||||
}
|
||||
if (!resp || !Array.isArray(resp.statuses)) {
|
||||
errLog('memory_sync_status_list: malformed response (missing statuses[]): %O', resp);
|
||||
throw new Error('Invalid response from openhuman.memory_sync_status_list: missing statuses[]');
|
||||
errLog(
|
||||
'memory_sync_status_list: malformed response (missing statuses[]), returning empty: %O',
|
||||
resp
|
||||
);
|
||||
return [];
|
||||
}
|
||||
log('memory_sync_status_list: received %d row(s)', resp.statuses.length);
|
||||
return resp.statuses;
|
||||
|
||||
@@ -42,7 +42,7 @@ pub async fn status_list_rpc(config: &Config) -> Result<RpcOutcome<StatusListRes
|
||||
tracing::debug!("[memory_sync_status][rpc] status_list");
|
||||
|
||||
let config = config.clone();
|
||||
let statuses: Vec<MemorySyncStatus> = tokio::task::spawn_blocking(move || {
|
||||
let statuses: Vec<MemorySyncStatus> = match tokio::task::spawn_blocking(move || {
|
||||
with_connection(&config, |conn| -> anyhow::Result<Vec<MemorySyncStatus>> {
|
||||
// Provider parsed from `source_id` prefix (substring before
|
||||
// first ':'); falls back to `source_kind` when no prefix.
|
||||
@@ -130,8 +130,23 @@ pub async fn status_list_rpc(config: &Config) -> Result<RpcOutcome<StatusListRes
|
||||
})
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("spawn_blocking join failed: {e}"))?
|
||||
.map_err(|e| format!("memory_tree DB access failed: {e:#}"))?;
|
||||
{
|
||||
Ok(Ok(rows)) => rows,
|
||||
// DB unavailable (open/migration failure) or query error: return empty
|
||||
// so the schema contract (`statuses` array) is always satisfied.
|
||||
Ok(Err(e)) => {
|
||||
tracing::warn!(
|
||||
"[memory_sync_status][rpc] DB query failed, returning empty statuses: {e:#}"
|
||||
);
|
||||
vec![]
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
"[memory_sync_status][rpc] spawn_blocking join error, returning empty statuses: {e}"
|
||||
);
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
|
||||
tracing::debug!(
|
||||
"[memory_sync_status][rpc] status_list returning {} row(s)",
|
||||
@@ -143,3 +158,41 @@ pub async fn status_list_rpc(config: &Config) -> Result<RpcOutcome<StatusListRes
|
||||
// `resp.statuses` directly, so any envelope here breaks parsing.
|
||||
Ok(RpcOutcome::new(StatusListResponse { statuses }, vec![]))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn status_list_response_serializes_statuses_array() {
|
||||
let resp = StatusListResponse { statuses: vec![] };
|
||||
let v = serde_json::to_value(&resp).expect("serialize");
|
||||
assert!(
|
||||
v.get("statuses").and_then(|s| s.as_array()).is_some(),
|
||||
"statuses must always be present as an array"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn status_list_response_empty_statuses_is_empty_array() {
|
||||
let resp = StatusListResponse { statuses: vec![] };
|
||||
let v = serde_json::to_value(&resp).expect("serialize");
|
||||
let arr = v["statuses"].as_array().unwrap();
|
||||
assert!(arr.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rpc_outcome_no_logs_serializes_bare_value() {
|
||||
// Validates the wire contract: with empty logs, into_cli_compatible_json
|
||||
// returns the value directly (not wrapped in { result, logs }).
|
||||
let resp = StatusListResponse { statuses: vec![] };
|
||||
let outcome = RpcOutcome::new(resp, vec![]);
|
||||
let json = outcome.into_cli_compatible_json().expect("serialize");
|
||||
assert!(
|
||||
json.get("statuses").is_some(),
|
||||
"bare value must have statuses at the top level"
|
||||
);
|
||||
assert!(json.get("result").is_none(), "must not be double-wrapped");
|
||||
assert!(json.get("logs").is_none(), "must not be double-wrapped");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user