Coverage Report

Created: 2026-01-25 15:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/noah/src/realizar/src/target.rs
Line
Count
Source
1
//! Multi-Target Deployment Support
2
//!
3
//! Per `docs/specifications/serve-deploy-apr.md` Section 6 and §11.4:
4
//! Support for Lambda, Docker, WASM Edge, and bare metal deployments.
5
//!
6
//! ## Supported Targets
7
//!
8
//! | Target | Features | Use Case |
9
//! |--------|----------|----------|
10
//! | Native | Full (SIMD, GPU, threads) | Bare metal, EC2 |
11
//! | Lambda | SIMD, no GPU | AWS Lambda ARM64 |
12
//! | Docker | Full | Container deployments |
13
//! | WASM | CPU-only, no threads | Cloudflare Workers |
14
//!
15
//! ## Usage
16
//!
17
//! ```rust,ignore
18
//! use realizar::target::{DeployTarget, TargetCapabilities};
19
//!
20
//! let target = DeployTarget::detect();
21
//! let caps = target.capabilities();
22
//!
23
//! if caps.supports_simd {
24
//!     // Use SIMD-accelerated inference
25
//! }
26
//! ```
27
28
use serde::{Deserialize, Serialize};
29
30
/// Deployment target enumeration
31
///
32
/// Per spec §6: Multi-target deployment support
33
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
34
pub enum DeployTarget {
35
    /// Native binary (bare metal, EC2, etc.)
36
    Native,
37
    /// AWS Lambda (ARM64 Graviton or `x86_64`)
38
    Lambda,
39
    /// Docker container
40
    Docker,
41
    /// WebAssembly (Cloudflare Workers, browser)
42
    Wasm,
43
}
44
45
impl DeployTarget {
46
    /// Detect current deployment target at runtime
47
    ///
48
    /// Detection heuristics:
49
    /// - WASM: `cfg!(target_arch = "wasm32")`
50
    /// - Lambda: `AWS_LAMBDA_FUNCTION_NAME` env var
51
    /// - Docker: `/.dockerenv` file exists
52
    /// - Native: fallback
53
    #[must_use]
54
1
    pub fn detect() -> Self {
55
        // WASM detection at compile time
56
        #[cfg(target_arch = "wasm32")]
57
        {
58
            return Self::Wasm;
59
        }
60
61
        #[cfg(not(target_arch = "wasm32"))]
62
        {
63
            // Lambda detection via environment
64
1
            if std::env::var("AWS_LAMBDA_FUNCTION_NAME").is_ok() {
65
0
                return Self::Lambda;
66
1
            }
67
68
            // Docker detection via /.dockerenv
69
1
            if std::path::Path::new("/.dockerenv").exists() {
70
0
                return Self::Docker;
71
1
            }
72
73
            // Default to native
74
1
            Self::Native
75
        }
76
1
    }
77
78
    /// Get capabilities for this target
79
    #[must_use]
80
11
    pub const fn capabilities(&self) -> TargetCapabilities {
81
11
        match self {
82
            // Native and Docker have full capabilities
83
4
            Self::Native | Self::Docker => TargetCapabilities {
84
4
                supports_simd: true,
85
4
                supports_gpu: true,
86
4
                supports_threads: true,
87
4
                supports_filesystem: true,
88
4
                supports_async_io: true,
89
4
                max_memory_mb: 0, // Unlimited (container limit applies for Docker)
90
4
            },
91
3
            Self::Lambda => TargetCapabilities {
92
3
                supports_simd: true,
93
3
                supports_gpu: false,
94
3
                supports_threads: true,
95
3
                supports_filesystem: false, // /tmp only
96
3
                supports_async_io: true,
97
3
                max_memory_mb: 10240, // Max Lambda memory
98
3
            },
99
4
            Self::Wasm => TargetCapabilities {
100
4
                supports_simd: false, // WASM SIMD limited
101
4
                supports_gpu: false,
102
4
                supports_threads: false,    // No rayon
103
4
                supports_filesystem: false, // Must use include_bytes!
104
4
                supports_async_io: false,   // Limited
105
4
                max_memory_mb: 128,         // Typical worker limit
106
4
            },
107
        }
108
11
    }
109
110
    /// Get target name as string
111
    #[must_use]
112
6
    pub const fn name(&self) -> &'static str {
113
6
        match self {
114
2
            Self::Native => "native",
115
1
            Self::Lambda => "lambda",
116
1
            Self::Docker => "docker",
117
2
            Self::Wasm => "wasm",
118
        }
119
6
    }
120
121
    /// Check if target supports a specific feature
122
    #[must_use]
123
6
    pub const fn supports(&self, feature: TargetFeature) -> bool {
124
6
        let caps = self.capabilities();
125
6
        match feature {
126
2
            TargetFeature::Simd => caps.supports_simd,
127
2
            TargetFeature::Gpu => caps.supports_gpu,
128
1
            TargetFeature::Threads => caps.supports_threads,
129
1
            TargetFeature::Filesystem => caps.supports_filesystem,
130
0
            TargetFeature::AsyncIo => caps.supports_async_io,
131
        }
132
6
    }
133
}
134
135
impl std::fmt::Display for DeployTarget {
136
2
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137
2
        write!(f, "{}", self.name())
138
2
    }
139
}
140
141
/// Target capabilities
142
///
143
/// Per spec §6.3: WASM Limitations table
144
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
145
#[allow(clippy::struct_excessive_bools)] // Capability flags are naturally boolean
146
pub struct TargetCapabilities {
147
    /// SIMD acceleration available (AVX2, NEON, etc.)
148
    pub supports_simd: bool,
149
    /// GPU acceleration available (wgpu)
150
    pub supports_gpu: bool,
151
    /// Multi-threading available (rayon)
152
    pub supports_threads: bool,
153
    /// Filesystem access available
154
    pub supports_filesystem: bool,
155
    /// Async I/O available
156
    pub supports_async_io: bool,
157
    /// Maximum memory in MB (0 = unlimited)
158
    pub max_memory_mb: u32,
159
}
160
161
impl TargetCapabilities {
162
    /// Check if all required features are available
163
    #[must_use]
164
5
    pub const fn has_all(&self, required: &[TargetFeature]) -> bool {
165
5
        let mut i = 0;
166
9
        while i < required.len() {
167
6
            let has = match required[i] {
168
2
                TargetFeature::Simd => self.supports_simd,
169
1
                TargetFeature::Gpu => self.supports_gpu,
170
2
                TargetFeature::Threads => self.supports_threads,
171
1
                TargetFeature::Filesystem => self.supports_filesystem,
172
0
                TargetFeature::AsyncIo => self.supports_async_io,
173
            };
174
6
            if !has {
175
2
                return false;
176
4
            }
177
4
            i += 1;
178
        }
179
3
        true
180
5
    }
181
}
182
183
/// Target feature flags
184
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
185
pub enum TargetFeature {
186
    /// SIMD acceleration
187
    Simd,
188
    /// GPU acceleration
189
    Gpu,
190
    /// Multi-threading
191
    Threads,
192
    /// Filesystem access
193
    Filesystem,
194
    /// Async I/O
195
    AsyncIo,
196
}
197
198
/// Docker build configuration
199
///
200
/// Per spec §6.2: Multi-stage Dockerfile patterns
201
#[derive(Debug, Clone, Serialize, Deserialize)]
202
pub struct DockerConfig {
203
    /// Base image for builder stage
204
    pub builder_image: String,
205
    /// Base image for runtime stage
206
    pub runtime_image: String,
207
    /// Target triple for cross-compilation
208
    pub target_triple: String,
209
    /// Whether to strip binary
210
    pub strip_binary: bool,
211
    /// Exposed port
212
    pub expose_port: u16,
213
}
214
215
impl Default for DockerConfig {
216
7
    fn default() -> Self {
217
7
        Self {
218
7
            builder_image: "rust:1.83".to_string(),
219
7
            runtime_image: "gcr.io/distroless/static-debian12:latest".to_string(),
220
7
            target_triple: "x86_64-unknown-linux-musl".to_string(),
221
7
            strip_binary: true,
222
7
            expose_port: 8080,
223
7
        }
224
7
    }
225
}
226
227
impl DockerConfig {
228
    /// Create config for ARM64 (Graviton)
229
    #[must_use]
230
1
    pub fn arm64() -> Self {
231
1
        Self {
232
1
            target_triple: "aarch64-unknown-linux-musl".to_string(),
233
1
            ..Self::default()
234
1
        }
235
1
    }
236
237
    /// Create config for minimal scratch image
238
    #[must_use]
239
2
    pub fn scratch() -> Self {
240
2
        Self {
241
2
            runtime_image: "scratch".to_string(),
242
2
            ..Self::default()
243
2
        }
244
2
    }
245
246
    /// Generate Dockerfile content
247
    #[must_use]
248
1
    pub fn generate_dockerfile(&self) -> String {
249
1
        format!(
250
1
            r#"# Auto-generated by realizar target module
251
1
# Per docs/specifications/serve-deploy-apr.md Section 6.2
252
1
253
1
# Stage 1: Build
254
1
FROM {builder} AS builder
255
1
WORKDIR /build
256
1
257
1
# Cache dependencies
258
1
COPY Cargo.toml Cargo.lock ./
259
1
RUN mkdir src && echo "fn main() {{}}" > src/main.rs
260
1
RUN cargo build --release --target {target}
261
1
RUN rm -rf src
262
1
263
1
# Build actual binary
264
1
COPY src ./src
265
1
RUN cargo build --release --target {target}
266
1
{strip}
267
1
268
1
# Stage 2: Runtime
269
1
FROM {runtime}
270
1
COPY --from=builder /build/target/{target}/release/realizar /serve
271
1
EXPOSE {port}
272
1
ENTRYPOINT ["/serve"]
273
1
"#,
274
            builder = self.builder_image,
275
            runtime = self.runtime_image,
276
            target = self.target_triple,
277
1
            strip = if self.strip_binary {
278
1
                format!(
279
1
                    "RUN strip target/{}/release/realizar || true",
280
                    self.target_triple
281
                )
282
            } else {
283
0
                String::new()
284
            },
285
            port = self.expose_port
286
        )
287
1
    }
288
289
    /// Estimated image size in MB
290
    #[must_use]
291
2
    pub fn estimated_size_mb(&self) -> u32 {
292
2
        let base_size = if self.runtime_image.contains("scratch") {
293
1
            0
294
1
        } else if self.runtime_image.contains("distroless/static") {
295
1
            2
296
0
        } else if self.runtime_image.contains("distroless/cc") {
297
0
            20
298
        } else {
299
0
            50 // Generic estimate
300
        };
301
302
        // Binary size estimate: ~10MB for release build
303
2
        let binary_size = if self.strip_binary { 5 } else { 
100
};
304
305
2
        base_size + binary_size
306
2
    }
307
}
308
309
/// WASM build configuration
310
///
311
/// Per spec §6.3: WASM Edge deployment
312
#[derive(Debug, Clone, Serialize, Deserialize)]
313
pub struct WasmConfig {
314
    /// wasm-pack target (web, bundler, nodejs)
315
    pub target: WasmTarget,
316
    /// Output directory
317
    pub out_dir: String,
318
    /// Enable WASM SIMD (experimental)
319
    pub enable_simd: bool,
320
}
321
322
/// WASM build targets
323
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
324
pub enum WasmTarget {
325
    /// Browser via ES modules
326
    Web,
327
    /// Bundler (webpack, etc.)
328
    Bundler,
329
    /// Node.js
330
    NodeJs,
331
}
332
333
impl WasmTarget {
334
    /// Get wasm-pack target flag
335
    #[must_use]
336
5
    pub const fn flag(&self) -> &'static str {
337
5
        match self {
338
3
            Self::Web => "web",
339
1
            Self::Bundler => "bundler",
340
1
            Self::NodeJs => "nodejs",
341
        }
342
5
    }
343
}
344
345
impl Default for WasmConfig {
346
5
    fn default() -> Self {
347
5
        Self {
348
5
            target: WasmTarget::Web,
349
5
            out_dir: "pkg".to_string(),
350
5
            enable_simd: false,
351
5
        }
352
5
    }
353
}
354
355
impl WasmConfig {
356
    /// Generate build command
357
    #[must_use]
358
2
    pub fn build_command(&self) -> String {
359
2
        let mut cmd = format!(
360
2
            "wasm-pack build --target {} --release --out-dir {}",
361
2
            self.target.flag(),
362
            self.out_dir
363
        );
364
365
2
        if self.enable_simd {
366
1
            cmd.push_str(" -- -C target-feature=+simd128");
367
1
        }
368
369
2
        cmd
370
2
    }
371
372
    /// Generate Cloudflare Worker template
373
    #[must_use]
374
1
    pub fn cloudflare_worker_template(&self) -> String {
375
1
        format!(
376
1
            r"// Auto-generated Cloudflare Worker
377
1
// Per docs/specifications/serve-deploy-apr.md Section 6.3
378
1
379
1
import init, {{ predict }} from './{}/realizar.js';
380
1
381
1
let initialized = false;
382
1
383
1
export default {{
384
1
  async fetch(request, env) {{
385
1
    if (!initialized) {{
386
1
      await init();
387
1
      initialized = true;
388
1
    }}
389
1
390
1
    if (request.method !== 'POST') {{
391
1
      return new Response('Method not allowed', {{ status: 405 }});
392
1
    }}
393
1
394
1
    try {{
395
1
      const body = await request.json();
396
1
      const result = predict(body.features);
397
1
398
1
      return new Response(JSON.stringify(result), {{
399
1
        headers: {{ 'Content-Type': 'application/json' }}
400
1
      }});
401
1
    }} catch (e) {{
402
1
      return new Response(JSON.stringify({{ error: e.message }}), {{
403
1
        status: 500,
404
1
        headers: {{ 'Content-Type': 'application/json' }}
405
1
      }});
406
1
    }}
407
1
  }}
408
1
}};
409
1
",
410
            self.out_dir
411
        )
412
1
    }
413
}
414
415
/// Build manifest for CI/CD
416
///
417
/// Per spec §11.4: Multi-target build support
418
#[derive(Debug, Clone, Serialize, Deserialize)]
419
pub struct BuildManifest {
420
    /// Version from Cargo.toml
421
    pub version: String,
422
    /// Git commit hash
423
    pub git_hash: Option<String>,
424
    /// Build timestamp
425
    pub build_time: String,
426
    /// Targets to build
427
    pub targets: Vec<BuildTarget>,
428
}
429
430
/// Individual build target
431
#[derive(Debug, Clone, Serialize, Deserialize)]
432
pub struct BuildTarget {
433
    /// Target name
434
    pub name: String,
435
    /// Rust target triple
436
    pub triple: String,
437
    /// Deploy target type
438
    pub deploy_target: DeployTarget,
439
    /// Build features to enable
440
    pub features: Vec<String>,
441
}
442
443
impl BuildManifest {
444
    /// Create default manifest with all targets
445
    #[must_use]
446
2
    pub fn default_all_targets() -> Self {
447
2
        Self {
448
2
            version: env!("CARGO_PKG_VERSION").to_string(),
449
2
            git_hash: None,
450
2
            build_time: String::new(),
451
2
            targets: vec![
452
2
                BuildTarget {
453
2
                    name: "linux-x86_64".to_string(),
454
2
                    triple: "x86_64-unknown-linux-musl".to_string(),
455
2
                    deploy_target: DeployTarget::Docker,
456
2
                    features: vec!["server".to_string()],
457
2
                },
458
2
                BuildTarget {
459
2
                    name: "linux-arm64".to_string(),
460
2
                    triple: "aarch64-unknown-linux-musl".to_string(),
461
2
                    deploy_target: DeployTarget::Lambda,
462
2
                    features: vec!["lambda".to_string()],
463
2
                },
464
2
                BuildTarget {
465
2
                    name: "wasm".to_string(),
466
2
                    triple: "wasm32-unknown-unknown".to_string(),
467
2
                    deploy_target: DeployTarget::Wasm,
468
2
                    features: vec![],
469
2
                },
470
2
            ],
471
2
        }
472
2
    }
473
}
474
475
#[cfg(test)]
476
mod tests {
477
    use super::*;
478
479
    // ==========================================================================
480
    // RED PHASE: Failing tests for multi-target support
481
    // Per EXTREME TDD: Write tests FIRST
482
    // ==========================================================================
483
484
    // --------------------------------------------------------------------------
485
    // Test: DeployTarget detection and capabilities
486
    // --------------------------------------------------------------------------
487
488
    #[test]
489
1
    fn test_deploy_target_detect_native() {
490
        // In test environment without Lambda/Docker env vars, should detect native
491
        // Note: This test may behave differently in CI with Docker
492
1
        let target = DeployTarget::detect();
493
        // Should be Native, Lambda, or Docker depending on environment
494
1
        assert!(
495
0
            matches!(
496
1
                target,
497
                DeployTarget::Native | DeployTarget::Lambda | DeployTarget::Docker
498
            ),
499
0
            "Should detect a valid non-WASM target"
500
        );
501
1
    }
502
503
    #[test]
504
1
    fn test_deploy_target_names() {
505
1
        assert_eq!(DeployTarget::Native.name(), "native");
506
1
        assert_eq!(DeployTarget::Lambda.name(), "lambda");
507
1
        assert_eq!(DeployTarget::Docker.name(), "docker");
508
1
        assert_eq!(DeployTarget::Wasm.name(), "wasm");
509
1
    }
510
511
    #[test]
512
1
    fn test_deploy_target_display() {
513
1
        assert_eq!(format!("{}", DeployTarget::Native), "native");
514
1
        assert_eq!(format!("{}", DeployTarget::Wasm), "wasm");
515
1
    }
516
517
    #[test]
518
1
    fn test_native_capabilities() {
519
1
        let caps = DeployTarget::Native.capabilities();
520
1
        assert!(caps.supports_simd);
521
1
        assert!(caps.supports_gpu);
522
1
        assert!(caps.supports_threads);
523
1
        assert!(caps.supports_filesystem);
524
1
        assert!(caps.supports_async_io);
525
1
        assert_eq!(caps.max_memory_mb, 0); // Unlimited
526
1
    }
527
528
    #[test]
529
1
    fn test_lambda_capabilities() {
530
1
        let caps = DeployTarget::Lambda.capabilities();
531
1
        assert!(caps.supports_simd);
532
1
        assert!(!caps.supports_gpu); // No GPU in Lambda
533
1
        assert!(caps.supports_threads);
534
1
        assert!(!caps.supports_filesystem); // /tmp only
535
1
        assert!(caps.supports_async_io);
536
1
        assert_eq!(caps.max_memory_mb, 10240);
537
1
    }
538
539
    #[test]
540
1
    fn test_wasm_capabilities() {
541
1
        let caps = DeployTarget::Wasm.capabilities();
542
1
        assert!(!caps.supports_simd); // Limited SIMD
543
1
        assert!(!caps.supports_gpu);
544
1
        assert!(!caps.supports_threads); // No rayon
545
1
        assert!(!caps.supports_filesystem); // Must use include_bytes!
546
1
        assert!(!caps.supports_async_io);
547
1
        assert_eq!(caps.max_memory_mb, 128);
548
1
    }
549
550
    #[test]
551
1
    fn test_deploy_target_supports_feature() {
552
1
        assert!(DeployTarget::Native.supports(TargetFeature::Simd));
553
1
        assert!(DeployTarget::Native.supports(TargetFeature::Gpu));
554
555
1
        assert!(DeployTarget::Lambda.supports(TargetFeature::Simd));
556
1
        assert!(!DeployTarget::Lambda.supports(TargetFeature::Gpu));
557
558
1
        assert!(!DeployTarget::Wasm.supports(TargetFeature::Threads));
559
1
        assert!(!DeployTarget::Wasm.supports(TargetFeature::Filesystem));
560
1
    }
561
562
    #[test]
563
1
    fn test_capabilities_has_all() {
564
1
        let native_caps = DeployTarget::Native.capabilities();
565
1
        assert!(native_caps.has_all(&[TargetFeature::Simd, TargetFeature::Gpu]));
566
1
        assert!(native_caps.has_all(&[TargetFeature::Threads, TargetFeature::Filesystem]));
567
568
1
        let wasm_caps = DeployTarget::Wasm.capabilities();
569
1
        assert!(!wasm_caps.has_all(&[TargetFeature::Simd]));
570
1
        assert!(!wasm_caps.has_all(&[TargetFeature::Threads]));
571
1
        assert!(wasm_caps.has_all(&[])); // Empty requirements
572
1
    }
573
574
    // --------------------------------------------------------------------------
575
    // Test: Docker configuration
576
    // --------------------------------------------------------------------------
577
578
    #[test]
579
1
    fn test_docker_config_default() {
580
1
        let config = DockerConfig::default();
581
1
        assert_eq!(config.builder_image, "rust:1.83");
582
1
        assert!(config.runtime_image.contains("distroless"));
583
1
        assert_eq!(config.target_triple, "x86_64-unknown-linux-musl");
584
1
        assert!(config.strip_binary);
585
1
        assert_eq!(config.expose_port, 8080);
586
1
    }
587
588
    #[test]
589
1
    fn test_docker_config_arm64() {
590
1
        let config = DockerConfig::arm64();
591
1
        assert!(config.target_triple.contains("aarch64"));
592
1
    }
593
594
    #[test]
595
1
    fn test_docker_config_scratch() {
596
1
        let config = DockerConfig::scratch();
597
1
        assert_eq!(config.runtime_image, "scratch");
598
1
    }
599
600
    #[test]
601
1
    fn test_docker_generate_dockerfile() {
602
1
        let config = DockerConfig::default();
603
1
        let dockerfile = config.generate_dockerfile();
604
605
1
        assert!(dockerfile.contains("FROM rust:1.83 AS builder"));
606
1
        assert!(dockerfile.contains("distroless"));
607
1
        assert!(dockerfile.contains("x86_64-unknown-linux-musl"));
608
1
        assert!(dockerfile.contains("EXPOSE 8080"));
609
1
        assert!(dockerfile.contains("strip"));
610
1
    }
611
612
    #[test]
613
1
    fn test_docker_estimated_size() {
614
1
        let distroless = DockerConfig::default();
615
1
        assert!(distroless.estimated_size_mb() < 20); // ~7MB
616
617
1
        let scratch = DockerConfig::scratch();
618
1
        assert!(scratch.estimated_size_mb() < 10); // ~5MB (binary only)
619
1
    }
620
621
    // --------------------------------------------------------------------------
622
    // Test: WASM configuration
623
    // --------------------------------------------------------------------------
624
625
    #[test]
626
1
    fn test_wasm_config_default() {
627
1
        let config = WasmConfig::default();
628
1
        assert_eq!(config.target, WasmTarget::Web);
629
1
        assert_eq!(config.out_dir, "pkg");
630
1
        assert!(!config.enable_simd);
631
1
    }
632
633
    #[test]
634
1
    fn test_wasm_target_flags() {
635
1
        assert_eq!(WasmTarget::Web.flag(), "web");
636
1
        assert_eq!(WasmTarget::Bundler.flag(), "bundler");
637
1
        assert_eq!(WasmTarget::NodeJs.flag(), "nodejs");
638
1
    }
639
640
    #[test]
641
1
    fn test_wasm_build_command() {
642
1
        let config = WasmConfig::default();
643
1
        let cmd = config.build_command();
644
645
1
        assert!(cmd.contains("wasm-pack build"));
646
1
        assert!(cmd.contains("--target web"));
647
1
        assert!(cmd.contains("--release"));
648
1
        assert!(cmd.contains("--out-dir pkg"));
649
1
    }
650
651
    #[test]
652
1
    fn test_wasm_build_command_with_simd() {
653
1
        let config = WasmConfig {
654
1
            enable_simd: true,
655
1
            ..WasmConfig::default()
656
1
        };
657
1
        let cmd = config.build_command();
658
659
1
        assert!(cmd.contains("simd128"));
660
1
    }
661
662
    #[test]
663
1
    fn test_wasm_cloudflare_worker_template() {
664
1
        let config = WasmConfig::default();
665
1
        let template = config.cloudflare_worker_template();
666
667
1
        assert!(template.contains("import init"));
668
1
        assert!(template.contains("predict"));
669
1
        assert!(template.contains("async fetch"));
670
1
        assert!(template.contains("application/json"));
671
1
    }
672
673
    // --------------------------------------------------------------------------
674
    // Test: Build manifest
675
    // --------------------------------------------------------------------------
676
677
    #[test]
678
1
    fn test_build_manifest_default() {
679
1
        let manifest = BuildManifest::default_all_targets();
680
681
1
        assert!(!manifest.version.is_empty());
682
1
        assert_eq!(manifest.targets.len(), 3);
683
684
        // Check target names
685
3
        let 
names1
:
Vec<_>1
=
manifest.targets.iter()1
.
map1
(|t| t.name.as_str()).
collect1
();
686
1
        assert!(names.contains(&"linux-x86_64"));
687
1
        assert!(names.contains(&"linux-arm64"));
688
1
        assert!(names.contains(&"wasm"));
689
1
    }
690
691
    #[test]
692
1
    fn test_build_manifest_targets() {
693
1
        let manifest = BuildManifest::default_all_targets();
694
695
1
        let x86_target = manifest
696
1
            .targets
697
1
            .iter()
698
1
            .find(|t| t.name == "linux-x86_64")
699
1
            .expect("test");
700
1
        assert_eq!(x86_target.deploy_target, DeployTarget::Docker);
701
702
1
        let arm_target = manifest
703
1
            .targets
704
1
            .iter()
705
2
            .
find1
(|t| t.name == "linux-arm64")
706
1
            .expect("test");
707
1
        assert_eq!(arm_target.deploy_target, DeployTarget::Lambda);
708
709
1
        let wasm_target = manifest
710
1
            .targets
711
1
            .iter()
712
3
            .
find1
(|t| t.name == "wasm")
713
1
            .expect("test");
714
1
        assert_eq!(wasm_target.deploy_target, DeployTarget::Wasm);
715
1
    }
716
717
    // --------------------------------------------------------------------------
718
    // Test: Serialization
719
    // --------------------------------------------------------------------------
720
721
    #[test]
722
1
    fn test_deploy_target_serialization() {
723
1
        let target = DeployTarget::Lambda;
724
1
        let json = serde_json::to_string(&target).expect("test");
725
1
        assert!(json.contains("Lambda"));
726
727
1
        let deserialized: DeployTarget = serde_json::from_str(&json).expect("test");
728
1
        assert_eq!(deserialized, DeployTarget::Lambda);
729
1
    }
730
731
    #[test]
732
1
    fn test_docker_config_serialization() {
733
1
        let config = DockerConfig::default();
734
1
        let json = serde_json::to_string(&config).expect("test");
735
736
1
        assert!(json.contains("rust:1.83"));
737
1
        assert!(json.contains("distroless"));
738
739
1
        let deserialized: DockerConfig = serde_json::from_str(&json).expect("test");
740
1
        assert_eq!(deserialized.expose_port, 8080);
741
1
    }
742
743
    #[test]
744
1
    fn test_wasm_config_serialization() {
745
1
        let config = WasmConfig::default();
746
1
        let json = serde_json::to_string(&config).expect("test");
747
748
1
        assert!(json.contains("Web"));
749
1
        assert!(json.contains("pkg"));
750
751
1
        let deserialized: WasmConfig = serde_json::from_str(&json).expect("test");
752
1
        assert_eq!(deserialized.target, WasmTarget::Web);
753
1
    }
754
}