kreuzberg 4.2.0 → 4.2.2
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.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +1 -1
- data/ext/kreuzberg_rb/native/Cargo.lock +26 -17
- data/lib/kreuzberg/cli.rb +16 -6
- data/lib/kreuzberg/cli_proxy.rb +3 -1
- data/lib/kreuzberg/config.rb +59 -28
- data/lib/kreuzberg/djot_content.rb +225 -0
- data/lib/kreuzberg/extraction_api.rb +20 -4
- data/lib/kreuzberg/result.rb +12 -2
- data/lib/kreuzberg/version.rb +1 -1
- data/lib/kreuzberg.rb +1 -0
- data/sig/kreuzberg.rbs +23 -11
- data/spec/binding/batch_spec.rb +6 -5
- data/spec/binding/config_spec.rb +1 -1
- data/spec/binding/error_recovery_spec.rb +3 -3
- data/spec/binding/tables_spec.rb +11 -2
- data/spec/unit/config/extraction_config_spec.rb +2 -2
- data/spec/unit/config/output_format_spec.rb +18 -18
- data/vendor/Cargo.toml +1 -1
- data/vendor/kreuzberg/Cargo.toml +3 -2
- data/vendor/kreuzberg/README.md +1 -1
- data/vendor/kreuzberg/src/api/error.rs +60 -0
- data/vendor/kreuzberg/src/api/handlers.rs +153 -32
- data/vendor/kreuzberg/src/api/mod.rs +2 -0
- data/vendor/kreuzberg/src/api/openapi.rs +141 -0
- data/vendor/kreuzberg/src/api/router.rs +24 -2
- data/vendor/kreuzberg/src/api/startup.rs +21 -1
- data/vendor/kreuzberg/src/api/types.rs +50 -4
- data/vendor/kreuzberg/src/core/config/processing.rs +8 -1
- data/vendor/kreuzberg/src/core/config_validation/sections.rs +16 -4
- data/vendor/kreuzberg/src/core/extractor/file.rs +1 -2
- data/vendor/kreuzberg/src/core/extractor/mod.rs +2 -1
- data/vendor/kreuzberg/src/core/io.rs +7 -7
- data/vendor/kreuzberg/src/core/mime.rs +4 -4
- data/vendor/kreuzberg/src/extraction/excel.rs +246 -9
- data/vendor/kreuzberg/src/extraction/pptx/parser.rs +6 -0
- data/vendor/kreuzberg/src/plugins/mod.rs +1 -0
- data/vendor/kreuzberg/src/plugins/registry/extractor.rs +251 -5
- data/vendor/kreuzberg/src/plugins/registry/ocr.rs +150 -2
- data/vendor/kreuzberg/src/plugins/registry/processor.rs +213 -5
- data/vendor/kreuzberg/src/plugins/registry/validator.rs +220 -4
- data/vendor/kreuzberg/src/plugins/startup_validation.rs +385 -0
- data/vendor/kreuzberg/tests/config_behavioral.rs +14 -12
- data/vendor/kreuzberg/tests/core_integration.rs +2 -4
- data/vendor/kreuzberg/tests/mime_detection.rs +3 -2
- data/vendor/kreuzberg/tests/pptx_regression_tests.rs +284 -1
- data/vendor/kreuzberg/tests/xlsx_metadata_extraction_test.rs +56 -0
- data/vendor/kreuzberg-ffi/kreuzberg.h +7 -2
- data/vendor/kreuzberg-ffi/src/helpers.rs +13 -1
- data/vendor/kreuzberg-ffi/src/lib.rs +8 -5
- data/vendor/kreuzberg-ffi/src/memory.rs +35 -1
- data/vendor/kreuzberg-ffi/src/types.rs +8 -5
- data/vendor/kreuzberg-tesseract/Cargo.toml +1 -1
- metadata +5 -2
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
//! Startup validation for plugin registries.
|
|
2
|
+
//!
|
|
3
|
+
//! This module provides diagnostics and health checks for plugins
|
|
4
|
+
//! at server startup, helping operators diagnose issues in containerized
|
|
5
|
+
//! environments like Kubernetes.
|
|
6
|
+
|
|
7
|
+
use crate::Result;
|
|
8
|
+
use crate::plugins::registry::{
|
|
9
|
+
get_document_extractor_registry, get_ocr_backend_registry, get_post_processor_registry, get_validator_registry,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/// Plugin health status information.
|
|
13
|
+
///
|
|
14
|
+
/// Contains diagnostic information about registered plugins for each type.
|
|
15
|
+
#[derive(Debug, Clone)]
|
|
16
|
+
pub struct PluginHealthStatus {
|
|
17
|
+
/// Number of registered OCR backends
|
|
18
|
+
pub ocr_backends_count: usize,
|
|
19
|
+
/// Names of registered OCR backends
|
|
20
|
+
pub ocr_backends: Vec<String>,
|
|
21
|
+
/// Number of registered document extractors
|
|
22
|
+
pub extractors_count: usize,
|
|
23
|
+
/// Names of registered document extractors
|
|
24
|
+
pub extractors: Vec<String>,
|
|
25
|
+
/// Number of registered post-processors
|
|
26
|
+
pub post_processors_count: usize,
|
|
27
|
+
/// Names of registered post-processors
|
|
28
|
+
pub post_processors: Vec<String>,
|
|
29
|
+
/// Number of registered validators
|
|
30
|
+
pub validators_count: usize,
|
|
31
|
+
/// Names of registered validators
|
|
32
|
+
pub validators: Vec<String>,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
impl PluginHealthStatus {
|
|
36
|
+
/// Check plugin health and return status.
|
|
37
|
+
///
|
|
38
|
+
/// This function reads all plugin registries and collects information
|
|
39
|
+
/// about registered plugins. It logs warnings if critical plugins are missing.
|
|
40
|
+
///
|
|
41
|
+
/// # Returns
|
|
42
|
+
///
|
|
43
|
+
/// `PluginHealthStatus` with counts and names of all registered plugins.
|
|
44
|
+
///
|
|
45
|
+
/// # Example
|
|
46
|
+
///
|
|
47
|
+
/// ```no_run
|
|
48
|
+
/// use kreuzberg::plugins::startup_validation::PluginHealthStatus;
|
|
49
|
+
///
|
|
50
|
+
/// #[tokio::main]
|
|
51
|
+
/// async fn main() {
|
|
52
|
+
/// let status = PluginHealthStatus::check();
|
|
53
|
+
/// println!("OCR backends: {:?}", status.ocr_backends);
|
|
54
|
+
/// }
|
|
55
|
+
/// ```
|
|
56
|
+
pub fn check() -> Self {
|
|
57
|
+
let ocr_registry = get_ocr_backend_registry();
|
|
58
|
+
let ocr_backends = ocr_registry.read().map(|r| r.list()).unwrap_or_default();
|
|
59
|
+
|
|
60
|
+
let extractor_registry = get_document_extractor_registry();
|
|
61
|
+
let extractors = extractor_registry.read().map(|r| r.list()).unwrap_or_default();
|
|
62
|
+
|
|
63
|
+
let processor_registry = get_post_processor_registry();
|
|
64
|
+
let post_processors = processor_registry.read().map(|r| r.list()).unwrap_or_default();
|
|
65
|
+
|
|
66
|
+
let validator_registry = get_validator_registry();
|
|
67
|
+
let validators = validator_registry.read().map(|r| r.list()).unwrap_or_default();
|
|
68
|
+
|
|
69
|
+
let ocr_backends_count = ocr_backends.len();
|
|
70
|
+
let extractors_count = extractors.len();
|
|
71
|
+
let post_processors_count = post_processors.len();
|
|
72
|
+
let validators_count = validators.len();
|
|
73
|
+
|
|
74
|
+
PluginHealthStatus {
|
|
75
|
+
ocr_backends_count,
|
|
76
|
+
ocr_backends,
|
|
77
|
+
extractors_count,
|
|
78
|
+
extractors,
|
|
79
|
+
post_processors_count,
|
|
80
|
+
post_processors,
|
|
81
|
+
validators_count,
|
|
82
|
+
validators,
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// Validate plugin registries at startup and emit diagnostic logs.
|
|
88
|
+
///
|
|
89
|
+
/// This function is designed to be called when the API server starts
|
|
90
|
+
/// to help diagnose configuration issues early. It checks:
|
|
91
|
+
///
|
|
92
|
+
/// - Whether OCR backends are registered (warns if none)
|
|
93
|
+
/// - Whether document extractors are registered (warns if none)
|
|
94
|
+
/// - Environment variables that might affect plugin initialization
|
|
95
|
+
/// - File permission issues in containerized environments
|
|
96
|
+
///
|
|
97
|
+
/// For Kubernetes deployments, this logs information that helps with
|
|
98
|
+
/// troubleshooting in the container logs.
|
|
99
|
+
///
|
|
100
|
+
/// # Returns
|
|
101
|
+
///
|
|
102
|
+
/// - `Ok(PluginHealthStatus)` with diagnostic information
|
|
103
|
+
/// - `Err(KreuzbergError)` if critical issues are detected (currently always succeeds)
|
|
104
|
+
///
|
|
105
|
+
/// # Example
|
|
106
|
+
///
|
|
107
|
+
/// ```no_run
|
|
108
|
+
/// use kreuzberg::plugins::startup_validation::validate_plugins_at_startup;
|
|
109
|
+
///
|
|
110
|
+
/// #[tokio::main]
|
|
111
|
+
/// async fn main() -> kreuzberg::Result<()> {
|
|
112
|
+
/// let status = validate_plugins_at_startup()?;
|
|
113
|
+
/// println!("Plugins ready: {} backends registered", status.ocr_backends_count);
|
|
114
|
+
/// Ok(())
|
|
115
|
+
/// }
|
|
116
|
+
/// ```
|
|
117
|
+
pub fn validate_plugins_at_startup() -> Result<PluginHealthStatus> {
|
|
118
|
+
let status = PluginHealthStatus::check();
|
|
119
|
+
|
|
120
|
+
// Log OCR backend status
|
|
121
|
+
if status.ocr_backends_count == 0 {
|
|
122
|
+
tracing::warn!(
|
|
123
|
+
"No OCR backends registered. OCR functionality will be unavailable. \
|
|
124
|
+
This is normal if OCR is not required. \
|
|
125
|
+
If OCR is needed, check that: \
|
|
126
|
+
1. The 'ocr' feature is enabled in Cargo.toml \
|
|
127
|
+
2. TESSDATA_PREFIX environment variable is set (e.g., /usr/share/tesseract-ocr/tessdata) \
|
|
128
|
+
3. Tessdata files exist and are readable (tessdata/*.traineddata) \
|
|
129
|
+
4. In containers, mount tessdata volume or install tesseract-ocr package. \
|
|
130
|
+
See https://docs.kreuzberg.dev/guides/docker/ for Kubernetes setup."
|
|
131
|
+
);
|
|
132
|
+
} else {
|
|
133
|
+
tracing::info!(
|
|
134
|
+
"OCR backends registered: [{}]. Ready for OCR processing.",
|
|
135
|
+
status.ocr_backends.join(", ")
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Log document extractor status
|
|
140
|
+
if status.extractors_count == 0 {
|
|
141
|
+
tracing::warn!(
|
|
142
|
+
"No document extractors registered. \
|
|
143
|
+
Document extraction will fail. \
|
|
144
|
+
This usually indicates a configuration issue. \
|
|
145
|
+
Ensure extractors are properly registered during initialization."
|
|
146
|
+
);
|
|
147
|
+
} else {
|
|
148
|
+
tracing::info!("Document extractors registered: [{}]", status.extractors.join(", "));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Log post-processor status
|
|
152
|
+
if status.post_processors_count > 0 {
|
|
153
|
+
tracing::info!("Post-processors registered: [{}]", status.post_processors.join(", "));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Log validator status
|
|
157
|
+
if status.validators_count > 0 {
|
|
158
|
+
tracing::info!("Validators registered: [{}]", status.validators.join(", "));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Check for common environment variables
|
|
162
|
+
check_environment_variables();
|
|
163
|
+
|
|
164
|
+
Ok(status)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/// Check and log relevant environment variables at startup.
|
|
168
|
+
///
|
|
169
|
+
/// Logs diagnostics about environment variables that affect plugin behavior,
|
|
170
|
+
/// particularly useful for Kubernetes deployments where configuration
|
|
171
|
+
/// is often done via environment variables.
|
|
172
|
+
fn check_environment_variables() {
|
|
173
|
+
// Check TESSDATA_PREFIX for OCR
|
|
174
|
+
match std::env::var("TESSDATA_PREFIX") {
|
|
175
|
+
Ok(path) => {
|
|
176
|
+
tracing::debug!("TESSDATA_PREFIX={}", path);
|
|
177
|
+
// Verify the path exists
|
|
178
|
+
if let Ok(metadata) = std::fs::metadata(&path) {
|
|
179
|
+
if metadata.is_dir() {
|
|
180
|
+
tracing::debug!(
|
|
181
|
+
"TESSDATA_PREFIX directory exists and is readable. \
|
|
182
|
+
Tesseract should find trained data files."
|
|
183
|
+
);
|
|
184
|
+
} else {
|
|
185
|
+
tracing::warn!(
|
|
186
|
+
"TESSDATA_PREFIX={} exists but is not a directory. \
|
|
187
|
+
Tesseract may fail to initialize.",
|
|
188
|
+
path
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
tracing::warn!(
|
|
193
|
+
"TESSDATA_PREFIX={} does not exist or is not readable. \
|
|
194
|
+
Tesseract may fail to initialize. \
|
|
195
|
+
Check directory permissions in containerized environments.",
|
|
196
|
+
path
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
Err(_) => {
|
|
201
|
+
tracing::debug!("TESSDATA_PREFIX not set. Tesseract will use system default paths.");
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Check for common Kubernetes/Docker volume mount points
|
|
206
|
+
if std::path::Path::new("/usr/share/tesseract-ocr/tessdata").exists() {
|
|
207
|
+
tracing::debug!("Found tessdata at system default: /usr/share/tesseract-ocr/tessdata");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Check RUST_LOG for debugging
|
|
211
|
+
if let Ok(log_level) = std::env::var("RUST_LOG") {
|
|
212
|
+
tracing::debug!("RUST_LOG={}", log_level);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
#[cfg(test)]
|
|
217
|
+
mod tests {
|
|
218
|
+
use super::*;
|
|
219
|
+
|
|
220
|
+
#[test]
|
|
221
|
+
fn test_plugin_health_status_check() {
|
|
222
|
+
let status = PluginHealthStatus::check();
|
|
223
|
+
// Just verify the status can be created (counts are always non-negative)
|
|
224
|
+
let _ = status.ocr_backends_count;
|
|
225
|
+
let _ = status.extractors_count;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
#[test]
|
|
229
|
+
fn test_validate_plugins_at_startup() {
|
|
230
|
+
// Initialize tracing for tests
|
|
231
|
+
let _ = tracing_subscriber::fmt()
|
|
232
|
+
.with_max_level(tracing::Level::DEBUG)
|
|
233
|
+
.with_test_writer()
|
|
234
|
+
.try_init();
|
|
235
|
+
|
|
236
|
+
let result = validate_plugins_at_startup();
|
|
237
|
+
assert!(result.is_ok());
|
|
238
|
+
let status = result.unwrap();
|
|
239
|
+
// Status created successfully (counts are always non-negative)
|
|
240
|
+
let _ = status.ocr_backends_count;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
#[test]
|
|
244
|
+
fn test_plugin_health_status_ocr_backends_empty() {
|
|
245
|
+
let status = PluginHealthStatus::check();
|
|
246
|
+
// Status is valid even with no backends
|
|
247
|
+
assert_eq!(status.ocr_backends.len(), status.ocr_backends_count);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
#[test]
|
|
251
|
+
fn test_plugin_health_status_extractors_empty() {
|
|
252
|
+
let status = PluginHealthStatus::check();
|
|
253
|
+
// Status is valid even with no extractors
|
|
254
|
+
assert_eq!(status.extractors.len(), status.extractors_count);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
#[test]
|
|
258
|
+
fn test_plugin_health_status_post_processors_empty() {
|
|
259
|
+
let status = PluginHealthStatus::check();
|
|
260
|
+
// Status is valid even with no post-processors
|
|
261
|
+
assert_eq!(status.post_processors.len(), status.post_processors_count);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
#[test]
|
|
265
|
+
fn test_plugin_health_status_validators_empty() {
|
|
266
|
+
let status = PluginHealthStatus::check();
|
|
267
|
+
// Status is valid even with no validators
|
|
268
|
+
assert_eq!(status.validators.len(), status.validators_count);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
#[test]
|
|
272
|
+
fn test_validate_plugins_at_startup_returns_status() {
|
|
273
|
+
let _ = tracing_subscriber::fmt()
|
|
274
|
+
.with_max_level(tracing::Level::DEBUG)
|
|
275
|
+
.with_test_writer()
|
|
276
|
+
.try_init();
|
|
277
|
+
|
|
278
|
+
let result = validate_plugins_at_startup();
|
|
279
|
+
assert!(result.is_ok());
|
|
280
|
+
|
|
281
|
+
let status = result.unwrap();
|
|
282
|
+
// Verify all fields are present
|
|
283
|
+
assert_eq!(status.ocr_backends.len(), status.ocr_backends_count);
|
|
284
|
+
assert_eq!(status.extractors.len(), status.extractors_count);
|
|
285
|
+
assert_eq!(status.post_processors.len(), status.post_processors_count);
|
|
286
|
+
assert_eq!(status.validators.len(), status.validators_count);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
#[test]
|
|
290
|
+
fn test_plugin_health_status_check_consistency() {
|
|
291
|
+
let status1 = PluginHealthStatus::check();
|
|
292
|
+
let status2 = PluginHealthStatus::check();
|
|
293
|
+
|
|
294
|
+
// Counts should be consistent between calls
|
|
295
|
+
assert_eq!(status1.ocr_backends_count, status2.ocr_backends_count);
|
|
296
|
+
assert_eq!(status1.extractors_count, status2.extractors_count);
|
|
297
|
+
assert_eq!(status1.post_processors_count, status2.post_processors_count);
|
|
298
|
+
assert_eq!(status1.validators_count, status2.validators_count);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
#[test]
|
|
302
|
+
fn test_validate_plugins_at_startup_with_logging() {
|
|
303
|
+
// Initialize tracing with test writer
|
|
304
|
+
let _ = tracing_subscriber::fmt()
|
|
305
|
+
.with_max_level(tracing::Level::INFO)
|
|
306
|
+
.with_test_writer()
|
|
307
|
+
.try_init();
|
|
308
|
+
|
|
309
|
+
let result = validate_plugins_at_startup();
|
|
310
|
+
assert!(result.is_ok());
|
|
311
|
+
|
|
312
|
+
// Verify status is returned
|
|
313
|
+
let status = result.unwrap();
|
|
314
|
+
assert!(status.ocr_backends_count > 0);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
#[test]
|
|
318
|
+
fn test_plugin_health_status_all_counts_valid() {
|
|
319
|
+
let status = PluginHealthStatus::check();
|
|
320
|
+
|
|
321
|
+
// All counts should be valid and consistent with vectors
|
|
322
|
+
assert_eq!(status.ocr_backends.len(), status.ocr_backends_count);
|
|
323
|
+
assert_eq!(status.extractors.len(), status.extractors_count);
|
|
324
|
+
assert_eq!(status.post_processors.len(), status.post_processors_count);
|
|
325
|
+
assert_eq!(status.validators.len(), status.validators_count);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
#[test]
|
|
329
|
+
fn test_plugin_health_status_vec_sizes_match_counts() {
|
|
330
|
+
let status = PluginHealthStatus::check();
|
|
331
|
+
|
|
332
|
+
// Vector sizes should match their counts
|
|
333
|
+
assert_eq!(status.ocr_backends.len(), status.ocr_backends_count);
|
|
334
|
+
assert_eq!(status.extractors.len(), status.extractors_count);
|
|
335
|
+
assert_eq!(status.post_processors.len(), status.post_processors_count);
|
|
336
|
+
assert_eq!(status.validators.len(), status.validators_count);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
#[test]
|
|
340
|
+
fn test_validate_plugins_at_startup_logs_warnings_and_info() {
|
|
341
|
+
let _ = tracing_subscriber::fmt()
|
|
342
|
+
.with_max_level(tracing::Level::DEBUG)
|
|
343
|
+
.with_test_writer()
|
|
344
|
+
.try_init();
|
|
345
|
+
|
|
346
|
+
// Call validation which should log warnings if no extractors
|
|
347
|
+
let result = validate_plugins_at_startup();
|
|
348
|
+
assert!(result.is_ok());
|
|
349
|
+
|
|
350
|
+
let status = result.unwrap();
|
|
351
|
+
assert_eq!(status.ocr_backends.len(), status.ocr_backends_count);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
#[test]
|
|
355
|
+
fn test_check_environment_variables_with_rust_log() {
|
|
356
|
+
let _ = tracing_subscriber::fmt()
|
|
357
|
+
.with_max_level(tracing::Level::DEBUG)
|
|
358
|
+
.with_test_writer()
|
|
359
|
+
.try_init();
|
|
360
|
+
|
|
361
|
+
// This test just verifies that check_environment_variables doesn't panic
|
|
362
|
+
let result = validate_plugins_at_startup();
|
|
363
|
+
assert!(result.is_ok());
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
#[test]
|
|
367
|
+
fn test_plugin_health_status_clone() {
|
|
368
|
+
let status1 = PluginHealthStatus::check();
|
|
369
|
+
let status2 = status1.clone();
|
|
370
|
+
|
|
371
|
+
// Cloned status should be equal to original
|
|
372
|
+
assert_eq!(status1.ocr_backends_count, status2.ocr_backends_count);
|
|
373
|
+
assert_eq!(status1.extractors_count, status2.extractors_count);
|
|
374
|
+
assert_eq!(status1.post_processors_count, status2.post_processors_count);
|
|
375
|
+
assert_eq!(status1.validators_count, status2.validators_count);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
#[test]
|
|
379
|
+
fn test_plugin_health_status_debug_format() {
|
|
380
|
+
let status = PluginHealthStatus::check();
|
|
381
|
+
let debug_str = format!("{:?}", status);
|
|
382
|
+
assert!(!debug_str.is_empty());
|
|
383
|
+
assert!(debug_str.contains("ocr_backends_count"));
|
|
384
|
+
}
|
|
385
|
+
}
|
|
@@ -46,6 +46,7 @@ async fn test_output_format_plain_produces_plain() {
|
|
|
46
46
|
|
|
47
47
|
/// Test output_format Markdown produces markdown formatting
|
|
48
48
|
#[tokio::test]
|
|
49
|
+
#[cfg(feature = "html")]
|
|
49
50
|
async fn test_output_format_markdown_produces_markdown() {
|
|
50
51
|
let html = b"<h1>Title</h1><p>Paragraph with <strong>bold</strong> text.</p>";
|
|
51
52
|
|
|
@@ -195,20 +196,20 @@ async fn test_chunking_overlap_creates_overlap() {
|
|
|
195
196
|
.await
|
|
196
197
|
.expect("Should extract successfully");
|
|
197
198
|
|
|
198
|
-
if let Some(chunks) = result.chunks
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
199
|
+
if let Some(chunks) = result.chunks
|
|
200
|
+
&& chunks.len() >= 2
|
|
201
|
+
{
|
|
202
|
+
// Check if adjacent chunks have overlapping text
|
|
203
|
+
let chunk1_end = &chunks[0].content[chunks[0].content.len().saturating_sub(15)..];
|
|
204
|
+
let chunk2_start = &chunks[1].content[..chunks[1].content.len().min(15)];
|
|
203
205
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
+
// There should be some overlap in the text
|
|
207
|
+
let overlap_found = chunk1_end.chars().any(|c| c != ' ') && chunk2_start.chars().any(|c| c != ' ');
|
|
206
208
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
209
|
+
assert!(
|
|
210
|
+
overlap_found,
|
|
211
|
+
"Adjacent chunks should have overlapping non-whitespace text"
|
|
212
|
+
);
|
|
212
213
|
}
|
|
213
214
|
}
|
|
214
215
|
|
|
@@ -318,6 +319,7 @@ async fn test_quality_processing_disabled_no_score() {
|
|
|
318
319
|
|
|
319
320
|
/// Test output_format combinations with result_format
|
|
320
321
|
#[tokio::test]
|
|
322
|
+
#[cfg(feature = "html")]
|
|
321
323
|
async fn test_output_format_with_element_based() {
|
|
322
324
|
let html = b"<p>First paragraph</p><p>Second paragraph</p>";
|
|
323
325
|
|
|
@@ -408,10 +408,8 @@ async fn test_nonexistent_file_error() {
|
|
|
408
408
|
let result = extract_file("/nonexistent/file.txt", None, &config).await;
|
|
409
409
|
|
|
410
410
|
assert!(result.is_err());
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
kreuzberg::KreuzbergError::Validation { .. }
|
|
414
|
-
));
|
|
411
|
+
// File validation returns Io error for missing files (NotFound)
|
|
412
|
+
assert!(matches!(result.unwrap_err(), kreuzberg::KreuzbergError::Io(_)));
|
|
415
413
|
}
|
|
416
414
|
|
|
417
415
|
/// Test error handling for unsupported MIME types.
|
|
@@ -327,9 +327,10 @@ async fn test_mime_detection_nonexistent_file() {
|
|
|
327
327
|
assert!(result.is_err(), "Should fail for nonexistent file");
|
|
328
328
|
|
|
329
329
|
let error = result.unwrap_err();
|
|
330
|
+
// File existence check returns Io error (NotFound), not Validation error
|
|
330
331
|
assert!(
|
|
331
|
-
matches!(error, kreuzberg::KreuzbergError::
|
|
332
|
-
"Should return
|
|
332
|
+
matches!(error, kreuzberg::KreuzbergError::Io(_)),
|
|
333
|
+
"Should return Io error for nonexistent file"
|
|
333
334
|
);
|
|
334
335
|
}
|
|
335
336
|
|