mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-08-25 12:54:09 +02:00
Fix cookie defaults on the bootstrapped workspace and tie order in ordered atoms
`Workspace::default()` is derived, so the first workspace was created with `setting_send_cookies` and `setting_store_cookies` false, contradicting the model's `default_true` serde defaults and the migration's DEFAULT TRUE. `createOrderedModelAtom` returned -1 for equal keys, an inconsistent comparator, so same-millisecond `http_response_event` rows came out of V8's sort reversed.
This commit is contained in:
@@ -0,0 +1,59 @@
|
|||||||
|
import { createStore } from "jotai";
|
||||||
|
import { expect, test } from "vitest";
|
||||||
|
import type { HttpResponseEvent } from "../bindings/gen_models";
|
||||||
|
import { httpResponseEventsAtom, modelStoreDataAtom } from "./atoms";
|
||||||
|
import { newStoreData } from "./util";
|
||||||
|
|
||||||
|
// The five setting events that every send writes, all within the same millisecond
|
||||||
|
const SETTING_NAMES = [
|
||||||
|
"validate_certificates",
|
||||||
|
"redirects",
|
||||||
|
"timeout",
|
||||||
|
"send_cookies",
|
||||||
|
"store_cookies",
|
||||||
|
];
|
||||||
|
|
||||||
|
function settingEvent(id: string, name: string, createdAt: string): HttpResponseEvent {
|
||||||
|
return {
|
||||||
|
model: "http_response_event",
|
||||||
|
id,
|
||||||
|
createdAt,
|
||||||
|
updatedAt: createdAt,
|
||||||
|
workspaceId: "wk_1",
|
||||||
|
responseId: "rs_1",
|
||||||
|
event: { type: "setting", name, value: "true" },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test("events with equal createdAt keep store (DB) insertion order", () => {
|
||||||
|
const store = createStore();
|
||||||
|
const data = newStoreData();
|
||||||
|
SETTING_NAMES.forEach((name, i) => {
|
||||||
|
data.http_response_event[`hre_${i}`] = settingEvent(
|
||||||
|
`hre_${i}`,
|
||||||
|
name,
|
||||||
|
"2026-08-17T00:00:00.123",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
store.set(modelStoreDataAtom, data);
|
||||||
|
|
||||||
|
const names = store.get(httpResponseEventsAtom).map((e) => {
|
||||||
|
return e.event.type === "setting" ? e.event.name : e.event.type;
|
||||||
|
});
|
||||||
|
expect(names).toEqual(SETTING_NAMES);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("events with distinct createdAt sort ascending", () => {
|
||||||
|
const store = createStore();
|
||||||
|
const data = newStoreData();
|
||||||
|
for (const [id, createdAt] of [
|
||||||
|
["hre_b", "2026-08-17T00:00:00.456"],
|
||||||
|
["hre_a", "2026-08-17T00:00:00.123"],
|
||||||
|
["hre_c", "2026-08-17T00:00:00.789"],
|
||||||
|
]) {
|
||||||
|
data.http_response_event[id!] = settingEvent(id!, "timeout", createdAt!);
|
||||||
|
}
|
||||||
|
store.set(modelStoreDataAtom, data);
|
||||||
|
|
||||||
|
expect(store.get(httpResponseEventsAtom).map((e) => e.id)).toEqual(["hre_a", "hre_b", "hre_c"]);
|
||||||
|
});
|
||||||
@@ -61,7 +61,9 @@ export function createOrderedModelAtom<M extends AnyModel["model"]>(
|
|||||||
const modelData = data[modelType] ?? {};
|
const modelData = data[modelType] ?? {};
|
||||||
return Object.values(modelData).sort(
|
return Object.values(modelData).sort(
|
||||||
(a: ExtractModel<AnyModel, M>, b: ExtractModel<AnyModel, M>) => {
|
(a: ExtractModel<AnyModel, M>, b: ExtractModel<AnyModel, M>) => {
|
||||||
const n = a[field] > b[field] ? 1 : -1;
|
// NOTE: ties must return 0, or the comparator is inconsistent and V8 reorders
|
||||||
|
// equal-keyed rows. Sort is stable, so 0 preserves store (DB) insertion order.
|
||||||
|
const n = a[field] === b[field] ? 0 : a[field] > b[field] ? 1 : -1;
|
||||||
return order === "desc" ? n * -1 : n;
|
return order === "desc" ? n * -1 : n;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -25,10 +25,14 @@ impl<'a> ClientDb<'a> {
|
|||||||
|
|
||||||
if workspaces.is_empty() {
|
if workspaces.is_empty() {
|
||||||
workspaces.push(self.upsert_workspace(
|
workspaces.push(self.upsert_workspace(
|
||||||
|
// NOTE: `Workspace::default()` is derived, so every `default_true` setting has
|
||||||
|
// to be set explicitly here or the first workspace gets `false`.
|
||||||
&Workspace {
|
&Workspace {
|
||||||
name: "Yaak".to_string(),
|
name: "Yaak".to_string(),
|
||||||
setting_follow_redirects: true,
|
setting_follow_redirects: true,
|
||||||
setting_request_message_size: crate::models::DEFAULT_REQUEST_MESSAGE_SIZE,
|
setting_request_message_size: crate::models::DEFAULT_REQUEST_MESSAGE_SIZE,
|
||||||
|
setting_send_cookies: true,
|
||||||
|
setting_store_cookies: true,
|
||||||
setting_validate_certificates: true,
|
setting_validate_certificates: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -207,3 +211,28 @@ pub fn default_headers() -> Vec<HttpRequestHeader> {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::init_in_memory;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bootstraps_first_workspace_with_default_true_settings() {
|
||||||
|
let (query_manager, _blob_manager, _rx) = init_in_memory().expect("Failed to init DB");
|
||||||
|
let db = query_manager.connect();
|
||||||
|
|
||||||
|
let workspaces = db.list_workspaces().expect("Failed to list workspaces");
|
||||||
|
let workspace = workspaces.first().expect("No workspace was bootstrapped");
|
||||||
|
|
||||||
|
// Every setting with a `default_true` serde default must be true, since
|
||||||
|
// this workspace is built in Rust and never goes through deserialization
|
||||||
|
assert!(workspace.setting_send_cookies, "setting_send_cookies");
|
||||||
|
assert!(workspace.setting_store_cookies, "setting_store_cookies");
|
||||||
|
assert!(workspace.setting_follow_redirects, "setting_follow_redirects");
|
||||||
|
assert!(workspace.setting_validate_certificates, "setting_validate_certificates");
|
||||||
|
assert_eq!(
|
||||||
|
workspace.setting_request_message_size,
|
||||||
|
crate::models::DEFAULT_REQUEST_MESSAGE_SIZE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user