- 1
#![allow(clippy::unwrap_used, clippy::expect_used)] - 2
- 3
use vak_config::load_with_trust; - 4
- 5
#[test] - 6
fn project_layer_retry_keys_reach_effective_config() { - 7
// Regression: merge_into dropped run_retry_* when layering project over - 8
// user config, so the file value silently fell back to the default. - 9
let dir = tempfile::tempdir().unwrap(); - 10
let project = dir.path().join(".vak"); - 11
std::fs::create_dir_all(&project).unwrap(); - 12
std::fs::write( - 13
project.join("config.toml"), - 14
"provider = \"opencode-zen\"\nmodel = \"x-preview-f-free\"\nrun_retry_attempts = 9\nrun_retry_base_backoff_ms = 1234\n", - 15
) - 16
.unwrap(); - 17
- 18
let cfg = load_with_trust(dir.path(), true).unwrap(); - 19
assert_eq!(cfg.run_retry_attempts, 9); - 20
assert_eq!(cfg.run_retry_base_backoff_ms, 1234); - 21
} - 22
- 23
#[test] - 24
fn unknown_config_keys_warn_instead_of_failing() { - 25
let dir = tempfile::tempdir().unwrap(); - 26
let project = dir.path().join(".vak"); - 27
std::fs::create_dir_all(&project).unwrap(); - 28
std::fs::write( - 29
project.join("config.toml"), - 30
"provider = \"opencode-zen\"\nmodel = \"m\"\nfuture_key = true\n", - 31
) - 32
.unwrap(); - 33
- 34
let cfg = load_with_trust(dir.path(), true).unwrap(); - 35
assert!( - 36
cfg.warnings.iter().any(|w| w.contains("future_key")), - 37
"typo'd keys must be visible: {:?}", - 38
cfg.warnings - 39
); - 40
} - 41
- 42
#[test] - 43
fn ui_defaults_apply_when_section_absent() { - 44
let dir = tempfile::tempdir().unwrap(); - 45
std::fs::create_dir_all(dir.path().join(".vak")).unwrap(); - 46
- 47
let cfg = load_with_trust(dir.path(), true).unwrap(); - 48
assert_eq!(cfg.ui.theme, "dark"); - 49
assert!(cfg.ui.bell); - 50
assert!(cfg.ui.keymap.is_empty()); - 51
} - 52
- 53
#[test] - 54
fn ui_keymap_overrides_merge_project_over_user() { - 55
let dir = tempfile::tempdir().unwrap(); - 56
let project = dir.path().join(".vak"); - 57
std::fs::create_dir_all(&project).unwrap(); - 58
std::fs::write( - 59
project.join("config.toml"), - 60
"[ui.keymap]\n\"Ctrl-P\" = \"exit\"\n", - 61
) - 62
.unwrap(); - 63
let cfg = load_with_trust(dir.path(), true).unwrap(); - 64
assert_eq!( - 65
cfg.ui.keymap.get("Ctrl-P").map(String::as_str), - 66
Some("exit") - 67
); - 68
} - 69
- 70
#[test] - 71
fn ui_layer_overrides_and_unknown_theme_normalizes_to_dark() { - 72
let dir = tempfile::tempdir().unwrap(); - 73
let project = dir.path().join(".vak"); - 74
std::fs::create_dir_all(&project).unwrap(); - 75
std::fs::write( - 76
project.join("config.toml"), - 77
"[ui]\ntheme = \"light\"\nbell = false\n", - 78
) - 79
.unwrap(); - 80
let cfg = load_with_trust(dir.path(), true).unwrap(); - 81
assert_eq!(cfg.ui.theme, "light"); - 82
assert!(!cfg.ui.bell); - 83
- 84
std::fs::write( - 85
project.join("config.toml"), - 86
"[ui]\ntheme = \"neon\"\nbell = true\n", - 87
) - 88
.unwrap(); - 89
let cfg = load_with_trust(dir.path(), true).unwrap(); - 90
assert_eq!(cfg.ui.theme, "dark"); - 91
assert!(cfg.ui.bell); - 92
assert!( - 93
cfg.warnings.iter().any(|w| w.contains("ui.theme")), - 94
"unknown theme must warn: {:?}", - 95
cfg.warnings - 96
); - 97
} - 98
- 99
#[test] - 100
fn designed_ui_themes_are_valid_config_values() { - 101
for theme in [ - 102
"neo", - 103
"rich", - 104
"teenage", - 105
"plain", - 106
"midnight", - 107
"synthwave", - 108
"forest", - 109
] { - 110
let dir = tempfile::tempdir().unwrap(); - 111
let project = dir.path().join(".vak"); - 112
std::fs::create_dir_all(&project).unwrap(); - 113
std::fs::write( - 114
project.join("config.toml"), - 115
format!("[ui]\ntheme = \"{theme}\"\n"), - 116
) - 117
.unwrap(); - 118
let cfg = load_with_trust(dir.path(), true).unwrap(); - 119
assert_eq!(cfg.ui.theme, theme); - 120
} - 121
} - 122
- 123
#[test] - 124
fn custom_theme_names_resolve_without_warning() { - 125
let dir = tempfile::tempdir().unwrap(); - 126
let project = dir.path().join(".vak"); - 127
std::fs::create_dir_all(&project).unwrap(); - 128
std::fs::write( - 129
project.join("config.toml"), - 130
"[ui]\ntheme = \"sunset\"\n[ui.themes.sunset]\naccent = \"#ff5500\"\ndim = \"grey\"\nbogus = 1\n", - 131
) - 132
.unwrap(); - 133
let cfg = load_with_trust(dir.path(), true).unwrap(); - 134
assert_eq!(cfg.ui.theme, "sunset"); - 135
assert_eq!( - 136
cfg.ui.themes.get("sunset").and_then(|t| t.get("accent")), - 137
Some(&"#ff5500".to_string()) - 138
); - 139
assert!( - 140
cfg.warnings - 141
.iter() - 142
.any(|w| w.contains("themes.sunset.bogus")), - 143
"unknown theme color must warn: {:?}", - 144
cfg.warnings - 145
); - 146
} - 147
- 148
#[test] - 149
fn composer_osc52_and_accessibility_layers_apply() { - 150
let dir = tempfile::tempdir().unwrap(); - 151
let project = dir.path().join(".vak"); - 152
std::fs::create_dir_all(&project).unwrap(); - 153
- 154
let cfg = load_with_trust(dir.path(), true).unwrap(); - 155
assert_eq!(cfg.ui.composer, "emacs"); - 156
assert!(!cfg.ui.osc52); - 157
assert!(!cfg.ui.accessibility.plain); - 158
- 159
std::fs::write( - 160
project.join("config.toml"), - 161
"[ui]\ncomposer = \"vim\"\nosc52 = true\n[ui.accessibility]\nplain = true\nreduced_motion = true\nbogus = 1\n", - 162
) - 163
.unwrap(); - 164
let cfg = load_with_trust(dir.path(), true).unwrap(); - 165
assert_eq!(cfg.ui.composer, "vim"); - 166
assert!(cfg.ui.osc52); - 167
assert!(cfg.ui.accessibility.plain); - 168
assert!(cfg.ui.accessibility.reduced_motion); - 169
assert!(!cfg.ui.accessibility.screen_reader); - 170
assert!( - 171
cfg.warnings - 172
.iter() - 173
.any(|w| w.contains("accessibility.bogus")), - 174
"unknown accessibility key must warn: {:?}", - 175
cfg.warnings - 176
); - 177
- 178
std::fs::write(project.join("config.toml"), "[ui]\ncomposer = \"dvorak\"\n").unwrap(); - 179
let cfg = load_with_trust(dir.path(), true).unwrap(); - 180
assert_eq!(cfg.ui.composer, "emacs"); - 181
assert!(cfg.warnings.iter().any(|w| w.contains("ui.composer"))); - 182
} - 183
- 184
#[test] - 185
fn stop_policy_defaults_on_and_layer_overrides_apply() { - 186
let dir = tempfile::tempdir().unwrap(); - 187
let cfg = load_with_trust(dir.path(), false).unwrap(); - 188
assert!(cfg.stop_policy.enabled); - 189
assert_eq!(cfg.stop_policy.max_blocks, 2); - 190
- 191
let project = dir.path().join(".vak"); - 192
std::fs::create_dir_all(&project).unwrap(); - 193
std::fs::write( - 194
project.join("config.toml"), - 195
"[stop_policy]\nenabled = true\nmax_blocks = 5\nbogus = 1\n", - 196
) - 197
.unwrap(); - 198
let cfg = load_with_trust(dir.path(), true).unwrap(); - 199
assert!(cfg.stop_policy.enabled); - 200
assert_eq!(cfg.stop_policy.max_blocks, 5); - 201
assert!( - 202
cfg.warnings.iter().any(|w| w.contains("stop_policy.bogus")), - 203
"unknown stop_policy key must warn: {:?}", - 204
cfg.warnings - 205
); - 206
- 207
std::fs::write( - 208
project.join("config.toml"), - 209
"[stop_policy]\nenabled = false\n", - 210
) - 211
.unwrap(); - 212
let cfg = load_with_trust(dir.path(), true).unwrap(); - 213
assert!(!cfg.stop_policy.enabled); - 214
} - 215
- 216
#[test] - 217
fn finops_caps_and_overrides_layer_with_unknown_key_warning() { - 218
let dir = tempfile::tempdir().unwrap(); - 219
let cfg = load_with_trust(dir.path(), false).unwrap(); - 220
assert!(cfg.finops.max_run_usd.is_none()); - 221
assert!(cfg.finops.price_overrides.is_empty()); - 222
- 223
let project = dir.path().join(".vak"); - 224
std::fs::create_dir_all(&project).unwrap(); - 225
std::fs::write( - 226
project.join("config.toml"), - 227
"[finops]\nmax_run_usd = 5.0\nmax_day_usd = 20.0\nbogus = 1\n\n[finops.price_overrides.custom-model]\ninput = 1.25\noutput = 6.0\n", - 228
) - 229
.unwrap(); - 230
let cfg = load_with_trust(dir.path(), true).unwrap(); - 231
assert_eq!(cfg.finops.max_run_usd, Some(5.0)); - 232
assert_eq!(cfg.finops.max_day_usd, Some(20.0)); - 233
let entry = cfg - 234
.finops - 235
.price_overrides - 236
.get("custom-model") - 237
.expect("override"); - 238
assert_eq!((entry.input, entry.output), (1.25, 6.0)); - 239
assert!( - 240
cfg.warnings.iter().any(|w| w.contains("finops.bogus")), - 241
"unknown finops key must warn: {:?}", - 242
cfg.warnings - 243
); - 244
- 245
// Pricing resolution honors the override over the heuristic table. - 246
assert_eq!( - 247
vak_config::resolve_usd_per_mtok("custom-model", &cfg.finops.price_overrides), - 248
Some((1.25, 6.0)) - 249
); - 250
} - 251
- 252
/// An untrusted project must not be able to redirect the release feed: the - 253
/// feed names the binary that replaces this one and supplies its own artifact - 254
/// checksums, so whoever picks the URL picks the integrity check too. - 255
#[test] - 256
fn untrusted_project_cannot_redirect_the_updater() { - 257
let dir = tempfile::tempdir().unwrap(); - 258
let project = dir.path().join(".vak"); - 259
std::fs::create_dir_all(&project).unwrap(); - 260
std::fs::write( - 261
project.join("config.toml"), - 262
"[update]\nurl = \"https://evil.invalid/feed.json\"\ninterval_hours = 1\n", - 263
) - 264
.unwrap(); - 265
- 266
let untrusted = load_with_trust(dir.path(), false).unwrap(); - 267
assert_eq!(untrusted.update.url, None, "feed URL must be stripped"); - 268
assert!( - 269
untrusted - 270
.warnings - 271
.iter() - 272
.any(|w| w.contains("not trusted") && w.contains("update")), - 273
"the strip must be announced: {:?}", - 274
untrusted.warnings - 275
); - 276
- 277
let trusted = load_with_trust(dir.path(), true).unwrap(); - 278
assert_eq!( - 279
trusted.update.url.as_deref(), - 280
Some("https://evil.invalid/feed.json"), - 281
"a trusted workspace keeps its own choice" - 282
); - 283
} - 284
- 285
/// An untrusted project must not be able to switch on the feed pipeline: it is - 286
/// an unattended runner (the scheduler fetches every tick) executing code with - 287
/// network access, so a cloned repository that could set `feeds.enabled` would - 288
/// be handing itself an unattended runner (invariant 15). - 289
#[test] - 290
fn untrusted_project_cannot_enable_the_feed_pipeline() { - 291
let dir = tempfile::tempdir().unwrap(); - 292
let project = dir.path().join(".vak"); - 293
std::fs::create_dir_all(&project).unwrap(); - 294
std::fs::write( - 295
project.join("config.toml"), - 296
"[feeds]\nenabled = true\ndefault_check_interval = \"5m\"\n", - 297
) - 298
.unwrap(); - 299
- 300
let untrusted = load_with_trust(dir.path(), false).unwrap(); - 301
assert!( - 302
!untrusted.feeds.enabled, - 303
"an untrusted project must not enable feeds" - 304
); - 305
assert!( - 306
untrusted - 307
.warnings - 308
.iter() - 309
.any(|w| w.contains("not trusted") && w.contains("feeds")), - 310
"the strip must be announced: {:?}", - 311
untrusted.warnings - 312
); - 313
- 314
let trusted = load_with_trust(dir.path(), true).unwrap(); - 315
assert!( - 316
trusted.feeds.enabled, - 317
"a trusted workspace keeps its own choice" - 318
); - 319
} - 320
- 321
/// `inherit_* = false` clears the corresponding lower layer during merge, so - 322
/// an untrusted project setting it would switch off the user's own hooks and - 323
/// MCP servers. Disabling a protection is as privileged as adding capability. - 324
#[test] - 325
fn untrusted_project_cannot_disable_inherited_capabilities() { - 326
let dir = tempfile::tempdir().unwrap(); - 327
let project = dir.path().join(".vak"); - 328
std::fs::create_dir_all(&project).unwrap(); - 329
std::fs::write( - 330
project.join("config.toml"), - 331
"[capabilities]\ninherit_hooks = false\ninherit_mcp = false\n\ - 332
inherit_skills = false\ninherit_plugins = false\n", - 333
) - 334
.unwrap(); - 335
- 336
let cfg = load_with_trust(dir.path(), false).unwrap(); - 337
assert!( - 338
cfg.capabilities.inherit_hooks, - 339
"global hooks stay inherited" - 340
); - 341
assert!(cfg.capabilities.inherit_mcp, "global MCP stays inherited"); - 342
assert!(cfg.capabilities.inherit_skills); - 343
assert!(cfg.capabilities.inherit_plugins); - 344
- 345
let trusted = load_with_trust(dir.path(), true).unwrap(); - 346
assert!( - 347
!trusted.capabilities.inherit_hooks, - 348
"a trusted workspace may still isolate itself" - 349
); - 350
} - 351
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.