## 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
    }