dirsql 0.3.35__tar.gz → 0.3.37__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. {dirsql-0.3.35 → dirsql-0.3.37}/Cargo.lock +1 -1
  2. {dirsql-0.3.35 → dirsql-0.3.37}/PKG-INFO +1 -1
  3. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/Cargo.toml +1 -1
  4. dirsql-0.3.37/packages/python/e2e-attestation.json +6 -0
  5. {dirsql-0.3.35/packages/python/tests/integration → dirsql-0.3.37/packages/python/tests/e2e}/interpret_subprocess.py +1 -1
  6. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/src/cli/mod.rs +9 -32
  7. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/src/cli/native_config.rs +14 -215
  8. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/src/cli/router.rs +9 -8
  9. {dirsql-0.3.35 → dirsql-0.3.37}/Cargo.toml +0 -0
  10. {dirsql-0.3.35 → dirsql-0.3.37}/README.md +0 -0
  11. {dirsql-0.3.35 → dirsql-0.3.37}/dirsql/__init__.py +0 -0
  12. {dirsql-0.3.35 → dirsql-0.3.37}/dirsql/_async.py +0 -0
  13. {dirsql-0.3.35 → dirsql-0.3.37}/dirsql/_dirsql.pyi +0 -0
  14. {dirsql-0.3.35 → dirsql-0.3.37}/dirsql/cli/__init__.py +0 -0
  15. {dirsql-0.3.35 → dirsql-0.3.37}/dirsql/cli/binary_path.py +0 -0
  16. {dirsql-0.3.35 → dirsql-0.3.37}/dirsql/cli/interpret/__init__.py +0 -0
  17. {dirsql-0.3.35 → dirsql-0.3.37}/dirsql/cli/interpret/dispatch_extract.py +0 -0
  18. {dirsql-0.3.35 → dirsql-0.3.37}/dirsql/cli/interpret/load_app.py +0 -0
  19. {dirsql-0.3.35 → dirsql-0.3.37}/dirsql/cli/interpret/run.py +0 -0
  20. {dirsql-0.3.35 → dirsql-0.3.37}/dirsql/cli/interpret/write_message.py +0 -0
  21. {dirsql-0.3.35 → dirsql-0.3.37}/dirsql/cli/is_windows.py +0 -0
  22. {dirsql-0.3.35 → dirsql-0.3.37}/dirsql/cli/main.py +0 -0
  23. {dirsql-0.3.35 → dirsql-0.3.37}/dirsql/py.typed +0 -0
  24. {dirsql-0.3.35 → dirsql-0.3.37}/dirsql/resolve_config.py +0 -0
  25. {dirsql-0.3.35 → dirsql-0.3.37}/docs/.claude/CLAUDE.md +0 -0
  26. {dirsql-0.3.35 → dirsql-0.3.37}/docs/.vitepress/config.ts +0 -0
  27. {dirsql-0.3.35 → dirsql-0.3.37}/docs/.vitepress/theme/index.ts +0 -0
  28. {dirsql-0.3.35 → dirsql-0.3.37}/docs/.vitepress/theme/lang.ts +0 -0
  29. {dirsql-0.3.35 → dirsql-0.3.37}/docs/AGENTS.md +0 -0
  30. {dirsql-0.3.35 → dirsql-0.3.37}/docs/api/index.md +0 -0
  31. {dirsql-0.3.35 → dirsql-0.3.37}/docs/cli/config.md +0 -0
  32. {dirsql-0.3.35 → dirsql-0.3.37}/docs/cli/http-api.md +0 -0
  33. {dirsql-0.3.35 → dirsql-0.3.37}/docs/cli/index.md +0 -0
  34. {dirsql-0.3.35 → dirsql-0.3.37}/docs/cli/init.md +0 -0
  35. {dirsql-0.3.35 → dirsql-0.3.37}/docs/cli/server.md +0 -0
  36. {dirsql-0.3.35 → dirsql-0.3.37}/docs/getting-started.md +0 -0
  37. {dirsql-0.3.35 → dirsql-0.3.37}/docs/guide/async.md +0 -0
  38. {dirsql-0.3.35 → dirsql-0.3.37}/docs/guide/crdt.md +0 -0
  39. {dirsql-0.3.35 → dirsql-0.3.37}/docs/guide/persistence.md +0 -0
  40. {dirsql-0.3.35 → dirsql-0.3.37}/docs/guide/querying.md +0 -0
  41. {dirsql-0.3.35 → dirsql-0.3.37}/docs/guide/tables.md +0 -0
  42. {dirsql-0.3.35 → dirsql-0.3.37}/docs/guide/watching.md +0 -0
  43. {dirsql-0.3.35 → dirsql-0.3.37}/docs/index.md +0 -0
  44. {dirsql-0.3.35 → dirsql-0.3.37}/docs/migrations.md +0 -0
  45. {dirsql-0.3.35 → dirsql-0.3.37}/docs/package.json +0 -0
  46. {dirsql-0.3.35 → dirsql-0.3.37}/docs/playwright.config.ts +0 -0
  47. {dirsql-0.3.35 → dirsql-0.3.37}/docs/pnpm-lock.yaml +0 -0
  48. {dirsql-0.3.35 → dirsql-0.3.37}/docs/pnpm-workspace.yaml +0 -0
  49. {dirsql-0.3.35 → dirsql-0.3.37}/docs/tests/integration/home.spec.ts +0 -0
  50. {dirsql-0.3.35 → dirsql-0.3.37}/docs/tests/integration/language-flag.spec.ts +0 -0
  51. {dirsql-0.3.35 → dirsql-0.3.37}/docs/tests/unit/config.test.ts +0 -0
  52. {dirsql-0.3.35 → dirsql-0.3.37}/docs/tests/unit/lang.test.ts +0 -0
  53. {dirsql-0.3.35 → dirsql-0.3.37}/docs/vitest.config.ts +0 -0
  54. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/README.md +0 -0
  55. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/conftest.py +0 -0
  56. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/.claude/CLAUDE.md +0 -0
  57. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/.vitepress/config.ts +0 -0
  58. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/.vitepress/theme/index.ts +0 -0
  59. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
  60. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/AGENTS.md +0 -0
  61. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/api/index.md +0 -0
  62. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/cli/config.md +0 -0
  63. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/cli/http-api.md +0 -0
  64. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/cli/index.md +0 -0
  65. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/cli/init.md +0 -0
  66. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/cli/server.md +0 -0
  67. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/getting-started.md +0 -0
  68. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/guide/async.md +0 -0
  69. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/guide/crdt.md +0 -0
  70. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/guide/persistence.md +0 -0
  71. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/guide/querying.md +0 -0
  72. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/guide/tables.md +0 -0
  73. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/guide/watching.md +0 -0
  74. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/index.md +0 -0
  75. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/migrations.md +0 -0
  76. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/package.json +0 -0
  77. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/playwright.config.ts +0 -0
  78. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/pnpm-lock.yaml +0 -0
  79. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/pnpm-workspace.yaml +0 -0
  80. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/tests/integration/home.spec.ts +0 -0
  81. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/tests/integration/language-flag.spec.ts +0 -0
  82. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/tests/unit/config.test.ts +0 -0
  83. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/tests/unit/lang.test.ts +0 -0
  84. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/docs/vitest.config.ts +0 -0
  85. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/src/lib.rs +0 -0
  86. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/tests/__init__.py +0 -0
  87. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/tests/conftest.py +0 -0
  88. {dirsql-0.3.35/packages/python/tests/integration → dirsql-0.3.37/packages/python/tests/e2e}/__fixtures__/data/a/meta.json +0 -0
  89. {dirsql-0.3.35/packages/python/tests/integration → dirsql-0.3.37/packages/python/tests/e2e}/__fixtures__/data/b/meta.json +0 -0
  90. {dirsql-0.3.35/packages/python/tests/integration → dirsql-0.3.37/packages/python/tests/e2e}/__fixtures__/dirsql.config.py +0 -0
  91. {dirsql-0.3.35/packages/python/tests/integration → dirsql-0.3.37/packages/python/tests/e2e}/__fixtures__/interpret/data/a/meta.json +0 -0
  92. {dirsql-0.3.35/packages/python/tests/integration → dirsql-0.3.37/packages/python/tests/e2e}/__fixtures__/interpret/data/b/meta.json +0 -0
  93. {dirsql-0.3.35/packages/python/tests/integration → dirsql-0.3.37/packages/python/tests/e2e}/__fixtures__/interpret/dirsql.config.py +0 -0
  94. {dirsql-0.3.35/packages/python/tests/integration → dirsql-0.3.37/packages/python/tests/e2e}/__fixtures__/interpret/dirsql.config_no_app.py +0 -0
  95. {dirsql-0.3.35/packages/python/tests/integration → dirsql-0.3.37/packages/python/tests/e2e}/__fixtures__/interpret/dirsql.config_raises.py +0 -0
  96. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/tests/e2e/__init__.py +0 -0
  97. {dirsql-0.3.35 → dirsql-0.3.37}/packages/python/tests/integration/__init__.py +0 -0
  98. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/Cargo.toml +0 -0
  99. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/README.md +0 -0
  100. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/benches/db_bench.rs +0 -0
  101. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/benches/differ_bench.rs +0 -0
  102. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/benches/matcher_bench.rs +0 -0
  103. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/benches/scanner_bench.rs +0 -0
  104. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/docs/api/index.md +0 -0
  105. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/docs/cli/config.md +0 -0
  106. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/docs/cli/http-api.md +0 -0
  107. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/docs/cli/index.md +0 -0
  108. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/docs/cli/init.md +0 -0
  109. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/docs/cli/server.md +0 -0
  110. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/docs/getting-started.md +0 -0
  111. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/docs/guide/async.md +0 -0
  112. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/docs/guide/crdt.md +0 -0
  113. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/docs/guide/persistence.md +0 -0
  114. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/docs/guide/querying.md +0 -0
  115. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/docs/guide/tables.md +0 -0
  116. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/docs/guide/watching.md +0 -0
  117. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/docs/index.md +0 -0
  118. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/docs/migrations.md +0 -0
  119. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/src/bin/dirsql.rs +0 -0
  120. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/src/cli/init.rs +0 -0
  121. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/src/cli/serialize.rs +0 -0
  122. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/src/cli/server.rs +0 -0
  123. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/src/config.rs +0 -0
  124. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/src/db.rs +0 -0
  125. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/src/differ.rs +0 -0
  126. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/src/lib.rs +0 -0
  127. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/src/matcher.rs +0 -0
  128. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/src/persist.rs +0 -0
  129. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/src/scanner.rs +0 -0
  130. {dirsql-0.3.35 → dirsql-0.3.37}/packages/rust/src/watcher.rs +0 -0
  131. {dirsql-0.3.35 → dirsql-0.3.37}/pyproject.toml +0 -0
@@ -499,7 +499,7 @@ dependencies = [
499
499
 
500
500
  [[package]]
501
501
  name = "dirsql-py-ext"
502
- version = "0.3.35"
502
+ version = "0.3.37"
503
503
  dependencies = [
504
504
  "dirsql",
505
505
  "pyo3",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dirsql
3
- Version: 0.3.35
3
+ Version: 0.3.37
4
4
  Requires-Dist: pytest>=8 ; extra == 'dev'
5
5
  Requires-Dist: pytest-describe>=2 ; extra == 'dev'
6
6
  Requires-Dist: pytest-asyncio>=0.23 ; extra == 'dev'
@@ -4,7 +4,7 @@ name = "dirsql-py-ext"
4
4
  # pypi/maturin handler can rewrite it via `write-version` before
5
5
  # `maturin build`. `pyproject.toml` declares `dynamic = ["version"]`
6
6
  # and maturin reads this field. Mirrors `packages/rust/Cargo.toml`.
7
- version = "0.3.35"
7
+ version = "0.3.37"
8
8
  edition.workspace = true
9
9
  publish = false
10
10
  readme = "README.md"
@@ -0,0 +1,6 @@
1
+ {
2
+ "command": "uv run python -m pytest tests/e2e -q",
3
+ "ran_at": 1782732207,
4
+ "exit_code": 0,
5
+ "commit": "6a2dcb8948ae61431b6357df46ce00e8848173ac"
6
+ }
@@ -1,4 +1,4 @@
1
- """Subprocess plumbing for the `dirsql interpret` integration tests.
1
+ """Subprocess plumbing for the `dirsql interpret` e2e tests.
2
2
 
3
3
  Kept in its own module (rather than `conftest.py` or inline in the
4
4
  test file) because these helpers are not pytest fixtures -- they are
@@ -160,7 +160,15 @@ pub enum ServerError {
160
160
  #[cfg(test)]
161
161
  mod tests {
162
162
  use super::*;
163
- use tempfile::TempDir;
163
+
164
+ // `From<DirSQL> for AppState` produces the `Ready` arm -- this is
165
+ // verified at the integration tier by `from_dirsql_yields_ready_state`
166
+ // in `tests/cli_integration.rs`, which builds a real `DirSQL` over a
167
+ // temp directory (so the initial scan runs) and asserts the public
168
+ // `AppState::Ready` variant. It lived here once but needed
169
+ // `std::fs::write` to populate the scanned directory, which the
170
+ // `testing-conventions` `unit lint` isolation rule forbids in a unit
171
+ // test (effectful std). The pure config-default test below stays inline.
164
172
 
165
173
  #[test]
166
174
  fn default_config_binds_localhost_7117_with_30s_timeout() {
@@ -169,35 +177,4 @@ mod tests {
169
177
  assert_eq!(cfg.port, 7117);
170
178
  assert_eq!(cfg.query_timeout, Duration::from_secs(30));
171
179
  }
172
-
173
- #[test]
174
- fn app_state_from_dirsql_is_ready() {
175
- // `AppState: !Debug` (it wraps a `DirSQL`). Assert the `Ready` variant
176
- // without a dead match arm by routing through `require_ready`, which
177
- // returns `Ok` only for `Ready` -- a branch-free `.is_ok()` check.
178
- let dir = TempDir::new().unwrap();
179
- // Write a matching file so the extract closure runs during the initial
180
- // scan (otherwise the closure body would be a dead coverage region).
181
- std::fs::write(dir.path().join("a.txt"), b"").unwrap();
182
- let db = DirSQL::with_ignore(
183
- dir.path(),
184
- vec![crate::Table::new(
185
- "CREATE TABLE t (name TEXT)",
186
- "*.txt",
187
- |_| {
188
- vec![crate::Row::from_iter([(
189
- "name".to_string(),
190
- crate::Value::Text("x".into()),
191
- )])]
192
- },
193
- )],
194
- Vec::<String>::new(),
195
- )
196
- .unwrap();
197
- let state: AppState = db.into();
198
- assert!(
199
- router::require_ready(&state).is_ok(),
200
- "From<DirSQL> must produce AppState::Ready",
201
- );
202
- }
203
180
  }
@@ -296,7 +296,16 @@ fn json_to_value(v: serde_json::Value) -> Value {
296
296
  mod tests {
297
297
  use super::*;
298
298
  use std::io::Cursor;
299
- use std::process::{Command, Stdio};
299
+
300
+ // The subprocess-driven tests (`InterpretHelper::from_child` against a
301
+ // real `bash`/`true` child, and the `build_dirsql` round-trips that
302
+ // spawn a fake helper and write real fixture files) live in
303
+ // `tests/native_config.rs`. They exercise effectful std
304
+ // (`std::process::Command`, `std::fs::write`) and so belong at the
305
+ // integration tier per the `testing-conventions` `unit lint` isolation
306
+ // rule. The pure wire-format tests below operate on in-memory
307
+ // `Cursor`/`Vec` streams and stay inline next to the private functions
308
+ // they cover.
300
309
 
301
310
  // -- parse_handshake -----------------------------------------------
302
311
 
@@ -511,160 +520,10 @@ mod tests {
511
520
  }
512
521
 
513
522
  // -- build_dirsql --------------------------------------------------
514
-
515
- // -- InterpretHelper end-to-end (subprocess via bash) -------------
516
-
517
- /// Spawn a fake helper that prints `handshake` once on stdout then
518
- /// emits one canned response per line received on stdin (an infinite
519
- /// loop until stdin closes). Returns the helper paired with the
520
- /// parsed [`NativeConfig`]. Drives the production
521
- /// `InterpretHelper::from_child` constructor so the post-spawn
522
- /// plumbing is exercised by these tests.
523
- fn spawn_fake_helper(
524
- handshake: &str,
525
- response_per_request: &str,
526
- ) -> (Arc<InterpretHelper>, NativeConfig) {
527
- let child = Command::new("bash")
528
- .arg("-c")
529
- .arg(format!(
530
- "printf '%s\\n' '{handshake}'; while IFS= read -r line; do printf '%s\\n' '{response_per_request}'; done",
531
- ))
532
- .stdin(Stdio::piped())
533
- .stdout(Stdio::piped())
534
- .spawn()
535
- .unwrap();
536
- InterpretHelper::from_child(child).unwrap()
537
- }
538
-
539
- #[test]
540
- fn build_dirsql_threads_persist_and_persist_path_into_the_builder() {
541
- // Cover the `if config.persist` and `if let Some(p) = config.persist_path`
542
- // branches in `build_dirsql`. A real persist build needs the cache file
543
- // to live under root, so point persist_path at a path inside the
544
- // tempdir and pass persist=true.
545
- let tmp = tempfile::TempDir::new().unwrap();
546
- std::fs::write(tmp.path().join("a.json"), b"{}").unwrap();
547
- let cache_path = tmp.path().join(".dirsql/cache.db");
548
-
549
- let handshake = format!(
550
- r#"{{"type":"config","state":{{"root":"{}","tables":[{{"ddl":"CREATE TABLE papers (title TEXT)","glob":"*.json"}}],"persist":true,"persist_path":"{}"}}}}"#,
551
- tmp.path().display(),
552
- cache_path.display(),
553
- );
554
- let (helper, config) = spawn_fake_helper(
555
- &handshake,
556
- r#"{"type":"result","id":1,"ok":true,"rows":[{"title":"y"}]}"#,
557
- );
558
- assert!(config.persist);
559
- assert_eq!(config.persist_path.as_ref().unwrap(), &cache_path);
560
-
561
- let db = build_dirsql(helper, config).unwrap();
562
- let rows = db.query("SELECT COUNT(*) AS n FROM papers").unwrap();
563
- assert_eq!(rows[0].get("n"), Some(&Value::Integer(1)));
564
- assert!(cache_path.exists(), "persist build should create the cache");
565
- }
566
-
567
- #[test]
568
- fn build_dirsql_threads_extensions_into_the_builder() {
569
- // A handshake whose `extensions` names a missing shared library must
570
- // fail the build, proving the parsed extensions reach the core's
571
- // load-at-startup path (enable -> load -> disable). (#229)
572
- let tmp = tempfile::TempDir::new().unwrap();
573
- std::fs::write(tmp.path().join("a.json"), b"{}").unwrap();
574
-
575
- let handshake = format!(
576
- r#"{{"type":"config","state":{{"root":"{}","tables":[{{"ddl":"CREATE TABLE papers (title TEXT)","glob":"*.json"}}],"extensions":[{{"path":"/nonexistent/dirsql-no-such-ext.so"}}]}}}}"#,
577
- tmp.path().display(),
578
- );
579
- let (helper, config) = spawn_fake_helper(
580
- &handshake,
581
- r#"{"type":"result","id":1,"ok":true,"rows":[{"title":"y"}]}"#,
582
- );
583
- assert_eq!(config.extensions.len(), 1);
584
- assert_eq!(
585
- config.extensions[0].path,
586
- PathBuf::from("/nonexistent/dirsql-no-such-ext.so"),
587
- );
588
-
589
- let err = match build_dirsql(helper, config) {
590
- Ok(_) => panic!("expected build to fail on a missing extension"),
591
- Err(e) => e,
592
- };
593
- assert!(err.contains("failed to load extension"), "got: {err}");
594
- }
595
-
596
- #[test]
597
- fn build_dirsql_round_trip_with_fake_helper_invokes_extract_per_matched_file() {
598
- // Create a tempdir with two matching files so the scan invokes
599
- // `extract` twice — exercising the closure created inside
600
- // `build_dirsql`, the `dispatch_extract` path through the
601
- // helper's IO mutex, and end-to-end DDL/glob wiring.
602
- let tmp = tempfile::TempDir::new().unwrap();
603
- std::fs::write(tmp.path().join("a.json"), b"{}").unwrap();
604
- std::fs::write(tmp.path().join("b.json"), b"{}").unwrap();
605
-
606
- let handshake = format!(
607
- r#"{{"type":"config","state":{{"root":"{}","tables":[{{"ddl":"CREATE TABLE papers (title TEXT)","glob":"*.json"}}]}}}}"#,
608
- tmp.path().display(),
609
- );
610
- let (helper, config) = spawn_fake_helper(
611
- &handshake,
612
- r#"{"type":"result","id":1,"ok":true,"rows":[{"title":"x"}]}"#,
613
- );
614
-
615
- let db = build_dirsql(helper, config).unwrap();
616
- let rows = db.query("SELECT COUNT(*) AS n FROM papers").unwrap();
617
- assert_eq!(rows.len(), 1);
618
- assert_eq!(rows[0].get("n"), Some(&Value::Integer(2)));
619
- }
620
-
621
- #[test]
622
- fn helper_extract_round_trip_succeeds_against_a_fake_subprocess() {
623
- let (helper, _config) = spawn_fake_helper(
624
- r#"{"type":"config","state":{"root":"/tmp","tables":[{"ddl":"CREATE TABLE papers (title TEXT)","glob":"*"}]}}"#,
625
- r#"{"type":"result","id":1,"ok":true,"rows":[{"title":"Alpha"}]}"#,
626
- );
627
-
628
- let rows = helper.extract("papers", "/x/a.json").unwrap();
629
- assert_eq!(rows.len(), 1);
630
- assert_eq!(rows[0].get("title"), Some(&Value::Text("Alpha".into())));
631
-
632
- // Second request increments the id counter and still works against
633
- // the canned response (the fake echoes the same line per request).
634
- let rows2 = helper.extract("papers", "/x/b.json").unwrap();
635
- assert_eq!(rows2.len(), 1);
636
- }
637
-
638
- #[test]
639
- fn helper_from_child_errors_when_stdin_was_not_piped() {
640
- // `Command::spawn` without `.stdin(Stdio::piped())` inherits the
641
- // parent's stdin — `child.stdin.take()` returns `None`, driving
642
- // the first `.ok_or_else` arm in `from_child`.
643
- let child = Command::new("true").spawn().unwrap();
644
- let err = match InterpretHelper::from_child(child) {
645
- Ok(_) => panic!("expected error when stdin is not piped"),
646
- Err(e) => e,
647
- };
648
- assert!(
649
- err.contains("failed to capture interpret stdin"),
650
- "got: {err}",
651
- );
652
- }
653
-
654
- #[test]
655
- fn helper_from_child_errors_when_stdout_was_not_piped() {
656
- // Pipe stdin but leave stdout inherited — `child.stdout.take()`
657
- // returns `None`, driving the second `.ok_or_else` arm.
658
- let child = Command::new("true").stdin(Stdio::piped()).spawn().unwrap();
659
- let err = match InterpretHelper::from_child(child) {
660
- Ok(_) => panic!("expected error when stdout is not piped"),
661
- Err(e) => e,
662
- };
663
- assert!(
664
- err.contains("failed to capture interpret stdout"),
665
- "got: {err}",
666
- );
667
- }
523
+ //
524
+ // The `build_dirsql` round-trips and the `InterpretHelper::from_child`
525
+ // success/error paths spawn a real subprocess (`bash`/`true`) and write
526
+ // real fixture files, so they live in `tests/native_config.rs`.
668
527
 
669
528
  /// A writer that fails every write with the given `io::ErrorKind`,
670
529
  /// used to exercise `dispatch_extract`'s IO-error arms.
@@ -720,64 +579,4 @@ mod tests {
720
579
  let err = dispatch_extract(&mut stdout, &mut stdin, 1, "t", "p").unwrap_err();
721
580
  assert!(err.contains("read from interpret"), "got: {err}");
722
581
  }
723
-
724
- #[test]
725
- fn helper_from_child_errors_when_subprocess_emits_no_handshake() {
726
- // A subprocess that exits without writing anything drives
727
- // `from_child` through to `parse_handshake`'s empty-line arm.
728
- let child = Command::new("true")
729
- .stdin(Stdio::piped())
730
- .stdout(Stdio::piped())
731
- .spawn()
732
- .unwrap();
733
- let err = match InterpretHelper::from_child(child) {
734
- Ok(_) => panic!("expected error from empty-stdout subprocess"),
735
- Err(e) => e,
736
- };
737
- assert!(
738
- err.contains("exited before sending handshake"),
739
- "got: {err}"
740
- );
741
- }
742
-
743
- #[test]
744
- fn build_dirsql_errors_when_ddl_has_no_table_name() {
745
- // The DDL is rejected by `parse_table_name` before any extract
746
- // closure is created, so the helper's IO is never touched. Use a
747
- // real child (`true` exits cleanly) just to satisfy the
748
- // `InterpretHelper` shape.
749
- let mut child = Command::new("true")
750
- .stdin(Stdio::piped())
751
- .stdout(Stdio::piped())
752
- .spawn()
753
- .unwrap();
754
- let stdin = child.stdin.take().unwrap();
755
- let stdout = child.stdout.take().unwrap();
756
- let helper = Arc::new(InterpretHelper {
757
- _child: child,
758
- io: Arc::new(Mutex::new(HelperIo {
759
- stdin: BufWriter::new(stdin),
760
- stdout: BufReader::new(stdout),
761
- })),
762
- next_id: AtomicU64::new(1),
763
- });
764
- let config = NativeConfig {
765
- root: PathBuf::from("/tmp"),
766
- tables: vec![HandshakeTable {
767
- ddl: "NOT VALID DDL".into(),
768
- glob: "*.json".into(),
769
- strict: false,
770
- }],
771
- ignore: Vec::new(),
772
- persist: false,
773
- persist_path: None,
774
- extensions: Vec::new(),
775
- };
776
- // `DirSQL` doesn't impl Debug, so we can't use `unwrap_err` directly.
777
- let err = match build_dirsql(helper, config) {
778
- Ok(_) => panic!("expected build_dirsql to fail on invalid DDL"),
779
- Err(e) => e,
780
- };
781
- assert!(err.contains("could not parse table name"), "got: {err}");
782
- }
783
582
  }
@@ -167,15 +167,16 @@ pub(super) fn error_response(status: StatusCode, message: impl Into<String>) ->
167
167
  #[cfg(test)]
168
168
  mod tests {
169
169
  use super::*;
170
- use crate::db::DbError;
171
170
 
172
- #[test]
173
- fn classify_core_error_is_bad_request() {
174
- // A SQLite-level failure is the client's fault (bad SQL), so it maps
175
- // to 400 rather than a server error.
176
- let err = DirSqlError::Core(DbError::SchemaMismatch("nope".into()));
177
- assert_eq!(classify_query_error(&err), StatusCode::BAD_REQUEST);
178
- }
171
+ // The `DirSqlError::Core => 400` arm of `classify_query_error` is
172
+ // exercised end-to-end at the integration tier by
173
+ // `post_query_malformed_sql_returns_400_not_500` in
174
+ // `tests/cli_integration.rs`, which posts malformed SQL to `/query` and
175
+ // asserts the 400. Constructing a `Core` value inline would require
176
+ // importing the first-party `crate::db::DbError`, which the
177
+ // `testing-conventions` `unit lint` isolation rule forbids (a unit test
178
+ // may reach only `super::` and pure `std`). The non-Core arm below is
179
+ // pure -- it builds a `super::DirSqlError::Lock` -- so it stays inline.
179
180
 
180
181
  #[test]
181
182
  fn classify_non_core_error_is_internal_server_error() {
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes