//! Centralized application constants //! //! Magic numbers that were scattered across modules. Storage-specific limits //! (file sizes, presign expiry, allowed types) remain in storage.rs since //! they're only used there. // -- Database -- pub const DB_POOL_MAX_CONNECTIONS: u32 = 25; pub const DB_POOL_MIN_CONNECTIONS: u32 = 2; pub const DB_ACQUIRE_TIMEOUT_SECS: u64 = 3; /// Rotate connections after 30 minutes to prevent stale sessions. pub const DB_MAX_LIFETIME_SECS: u64 = 1800; /// Prune idle connections after 10 minutes. pub const DB_IDLE_TIMEOUT_SECS: u64 = 600; // -- Sessions -- pub const SESSION_EXPIRY_DAYS: i64 = 7; /// Skip DB touch if validated within this window. Doubles as the upper bound on /// session-revocation lag (admin suspend, logout-everywhere, password change) — /// shorter = tighter revocation, slightly more DB load on the auth hot path. pub const SESSION_TOUCH_CACHE_SECS: u64 = 5; // -- Login security -- pub const MAX_LOGIN_ATTEMPTS: i32 = 5; pub const LOCKOUT_MINUTES: i64 = 15; /// How long a half-completed login (password verified, awaiting 2FA) stays /// valid before the user must re-enter their password. Defends against the /// "unattended browser one TOTP from logged in" failure mode. pub const PENDING_2FA_TTL_SECS: i64 = 600; // -- Email link expiry (seconds) -- pub const PASSWORD_RESET_EXPIRY_SECS: i64 = 900; // 15 minutes pub const EMAIL_VERIFICATION_EXPIRY_SECS: i64 = 86400; // 24 hours pub const ACCOUNT_DELETION_EXPIRY_SECS: i64 = 3600; // 1 hour // -- Stripe fees (for display only — actual fees set by Stripe) -- pub const STRIPE_FEE_PERCENTAGE: f64 = 0.029; // 2.9% pub const STRIPE_FEE_FIXED_CENTS: f64 = 30.0; // $0.30 /// Stripe minimum charge amount in cents (USD). Charges below this are rejected. pub const STRIPE_MINIMUM_CHARGE_CENTS: i64 = 50; // $0.50 // -- Page / query limits -- pub const DASHBOARD_TRANSACTION_LIMIT: i64 = 100; // -- SyncKit -- pub const SYNCKIT_JWT_EXPIRY_SECS: i64 = 7 * 24 * 3600; // 7 days pub const SYNCKIT_PUSH_MAX_CHANGES: usize = 500; pub const SYNCKIT_PULL_PAGE_SIZE: i64 = 500; pub const SYNCKIT_API_KEY_LENGTH: usize = 32; // 32 bytes = 64 hex chars pub const SYNC_LOG_RETAIN_DAYS: i64 = 90; pub const SYNC_LOG_COMPACT_MIN_AGE_DAYS: i64 = 7; // Safety margin for cursor-based compaction pub const SYNCKIT_MAX_BLOB_SIZE_BYTES: i64 = 500 * 1024 * 1024; // 500 MB pub const SYNCKIT_MAX_BLOB_STORAGE_BYTES: i64 = 10 * 1024 * 1024 * 1024; // 10 GB per user per app pub const SYNCKIT_MAX_DEVICES_PER_APP: i64 = 50; // Max devices per user per app pub const SYNCKIT_BLOB_PRESIGN_EXPIRY_SECS: u64 = 3600; // 1 hour pub const SYNCKIT_MAX_SSE_CONNECTIONS_PER_USER: usize = 10; pub const SYNCKIT_ROTATION_STALE_HOURS: i64 = 24; pub const SYNCKIT_ROTATION_BATCH_MAX: usize = 500; // -- Subscriptions -- pub const MIN_SUBSCRIPTION_PRICE_CENTS: i32 = 100; // $1.00 minimum // -- OAuth -- pub const OAUTH_CODE_EXPIRY_SECS: i64 = 600; // 10 minutes pub const OAUTH_CODE_LENGTH: usize = 32; // 32 bytes = 64 hex chars // -- Health monitoring -- pub const HEALTH_CHECK_INTERVAL_SECS: u64 = 60; pub const ALERT_COOLDOWN_SECS: u64 = 300; // 5 minutes pub const HEALTH_HISTORY_RETAIN_DAYS: i64 = 90; // -- Scheduled publish -- pub const SCHEDULER_INTERVAL_SECS: u64 = 60; // -- TOTP / 2FA -- pub const TOTP_SKEW: u8 = 1; // Allow +/-1 time step (+/-30s) pub const TOTP_STEP: u64 = 30; // 30-second windows pub const TOTP_DIGITS: usize = 6; // 6-digit codes pub const BACKUP_CODE_COUNT: usize = 10; // Generate 10 codes pub const BACKUP_CODE_LENGTH: usize = 8; // 8 alphanumeric chars // -- Anti-enumeration -- pub const USERNAME_CHECK_DELAY_MS: u64 = 400; // -- Rate limiting -- // Auth endpoints (login, join): burst 5, then 2/sec. // fast-tests: relaxed to burst 20 so lockout tests can fire 5+ attempts without hitting rate limiter. #[cfg(not(feature = "fast-tests"))] pub const AUTH_RATE_LIMIT_MS: u64 = 500; #[cfg(not(feature = "fast-tests"))] pub const AUTH_RATE_LIMIT_BURST: u32 = 5; #[cfg(feature = "fast-tests")] pub const AUTH_RATE_LIMIT_MS: u64 = 10; #[cfg(feature = "fast-tests")] pub const AUTH_RATE_LIMIT_BURST: u32 = 20; // Username validation: burst 10, then 1/sec pub const VALIDATE_RATE_LIMIT_PER_SEC: u64 = 1; pub const VALIDATE_RATE_LIMIT_BURST: u32 = 10; // API write endpoints (CRUD): burst 30, then 2/sec pub const API_WRITE_RATE_LIMIT_MS: u64 = 500; pub const API_WRITE_RATE_LIMIT_BURST: u32 = 30; // API read endpoints (GET): burst 60, then 10/sec (prevents enumeration) pub const API_READ_RATE_LIMIT_MS: u64 = 100; pub const API_READ_RATE_LIMIT_BURST: u32 = 60; // API export endpoints: burst 3, then 1/sec pub const API_EXPORT_RATE_LIMIT_PER_SEC: u64 = 1; pub const API_EXPORT_RATE_LIMIT_BURST: u32 = 3; // Guest checkout (public, no auth): burst 10, then 1/sec pub const GUEST_CHECKOUT_RATE_LIMIT_PER_SEC: u64 = 1; pub const GUEST_CHECKOUT_RATE_LIMIT_BURST: u32 = 10; // License key validation (public): burst 20, then 5/sec pub const LICENSE_KEY_RATE_LIMIT_MS: u64 = 200; pub const LICENSE_KEY_RATE_LIMIT_BURST: u32 = 20; // File upload: burst 10, then 2/sec pub const UPLOAD_RATE_LIMIT_MS: u64 = 500; pub const UPLOAD_RATE_LIMIT_BURST: u32 = 10; // OAuth authorize/token: burst 5/10, then 2/sec pub const OAUTH_RATE_LIMIT_MS: u64 = 500; pub const OAUTH_RATE_LIMIT_BURST: u32 = 5; pub const OAUTH_TOKEN_RATE_LIMIT_MS: u64 = 500; pub const OAUTH_TOKEN_RATE_LIMIT_BURST: u32 = 10; // SyncKit auth: burst 5, then 1/sec pub const SYNCKIT_AUTH_RATE_LIMIT_PER_SEC: u64 = 1; pub const SYNCKIT_AUTH_RATE_LIMIT_BURST: u32 = 5; // SyncKit sync (push/pull) — per-IP: burst 30, then 10/sec pub const SYNCKIT_SYNC_RATE_LIMIT_MS: u64 = 100; pub const SYNCKIT_SYNC_RATE_LIMIT_BURST: u32 = 30; // SyncKit sync — per-app: burst 60, then 20/sec (higher than per-IP because // a single app may have many users behind different IPs) pub const SYNCKIT_APP_RATE_LIMIT_MS: u64 = 50; pub const SYNCKIT_APP_RATE_LIMIT_BURST: u32 = 60; // 2FA verification: burst 5, then 2/sec (same as auth) pub const TWO_FACTOR_RATE_LIMIT_MS: u64 = 500; pub const TWO_FACTOR_RATE_LIMIT_BURST: u32 = 5; // Dashboard tab reads: generous but bounded (5/sec, burst 20) pub const DASHBOARD_READ_RATE_LIMIT_MS: u64 = 200; pub const DASHBOARD_READ_RATE_LIMIT_BURST: u32 = 20; // -- Pagination -- pub const DISCOVER_PAGE_SIZE: u32 = 25; pub const FEED_PAGE_SIZE: u32 = 25; pub const PAGINATION_WINDOW_SIZE: u32 = 5; // -- Creator broadcast fan-out -- /// Max concurrent in-flight email sends per broadcast. The outer worker /// task spawns up to this many child tasks, then waits on one to drain /// before spawning the next. pub const BROADCAST_PARALLELISM: usize = 16; /// Delay between successive broadcast send-task spawns. Spreads Postmark /// API load when a creator with thousands of followers fires a broadcast /// — at parallelism 16 + 100 ms cadence, steady-state is ~10 sends/sec. pub const BROADCAST_CHUNK_DELAY_MS: u64 = 100; /// Recipient cap per broadcast send. Above this, the request is refused /// with an instruction to contact support. Bounds Postmark spend exposure /// from any single approved creator. Founder-window cohort is well under /// this; the cap is the floor we'd lift on request, not the ceiling. pub const BROADCAST_MAX_RECIPIENTS: usize = 10_000; /// Cap on buyer-departure notification fan-out per creator-deletion event. /// Account deletion notifies historical buyers about content removal. A /// creator with millions of completed sales should not turn one deletion /// into a Postmark bomb. The cap bounds both the in-memory buyer list and /// total outbound email volume; if hit, we log a warning and notify the /// oldest-buyers slice the SQL chose. pub const BUYER_DEPARTURE_MAX_NOTIFICATIONS: i64 = 50_000; // -- File scanning -- pub const SCAN_MAX_MEMORY_BYTES: usize = 100 * 1024 * 1024; // 100 MB in-memory threshold pub const SCAN_MAX_CONCURRENT: usize = 4; // Max concurrent file scans (each can use up to 100 MB RAM) pub const SCAN_WORKER_COUNT: usize = 2; // Background worker tasks draining scan_jobs queue /// Retention window for terminal-state (`done`, `failed`) `scan_jobs` rows. /// Queued/running rows are operational queue state and not affected. pub const SCAN_JOB_RETENTION_DAYS: u32 = 30; /// Directory under which the scanner spools large objects to tempfiles /// before invoking path/stream-based layer entries. On production, systemd /// provisions this via `StateDirectory=mnw/scan-spool` so the path resolves /// to `/var/lib/mnw/scan-spool`. Override with `MNW_SCAN_SPOOL_DIR` for dev. pub const SCAN_SPOOL_DIR: &str = "/var/lib/makenotwork/scan-spool"; /// Files in `SCAN_SPOOL_DIR` older than this are considered orphaned /// (a panic, OOM, or hard kill left them behind) and reaped on the /// next sweep. RAII drop in `SpoolHandle` covers the live path; this /// covers process-death. pub const SCAN_SPOOL_ORPHAN_AGE_SECS: u64 = 3600; /// Hard cap on a single spooled object. Above this, the scanner refuses /// the job rather than risk filling the volume. 8 GiB matches the largest /// payload the upload tier currently allows. pub const SCAN_SPOOL_MAX_BYTES: u64 = 8 * 1024 * 1024 * 1024; /// Minimum free space the spool volume must retain after writing the /// pending object. The scanner refuses if `statvfs(free) - expected_size` /// would drop below this threshold. pub const SCAN_SPOOL_FREE_RESERVE_BYTES: u64 = 2 * 1024 * 1024 * 1024; // -- Caddy on-demand TLS -- // Caps concurrent cache-miss DB lookups in `/api/domains/caddy-ask`. Cache hits // are unbounded (DashMap). At capacity, the handler returns 503 so Caddy retries // later instead of stampeding the DB pool or driving ACME issuance for garbage // domains. Sized small because the slow path is one indexed lookup. pub const CADDY_ASK_MAX_CONCURRENT: usize = 8; pub const SCAN_ZIP_MAX_RATIO: f64 = 100.0; // Max compression ratio before ZIP bomb pub const SCAN_ZIP_MAX_DEPTH: u32 = 2; // Max nested archives (detection is 1 level deep; decompressed size limit is the primary defense) pub const SCAN_ZIP_MAX_UNCOMPRESSED: u64 = 2 * 1024 * 1024 * 1024; // 2 GB uncompressed limit pub const SCAN_MALWAREBAZAAR_TIMEOUT_SECS: u64 = 5; pub const SCAN_CLAMAV_TIMEOUT_SECS: u64 = 30; // -- Invite system -- pub const INVITES_ENABLED: bool = true; pub const INVITE_LIMIT_PER_CREATOR: i64 = 5; // max unredeemed codes per creator // -- Git source browser -- pub const GIT_MAX_FILE_SIZE_BYTES: usize = 1_024_000; // 1MB display limit pub const GIT_COMMITS_PER_PAGE: usize = 30; pub const GIT_DIFF_MAX_FILES: usize = 20; // Inline diff hunks for first N files pub const GIT_DIFF_MAX_LINES: usize = 500; // Per-file line cap for diff display pub const GIT_REPOS_PER_PAGE: usize = 30; pub const GIT_FILE_LOG_MAX_WALK: usize = 1000; // Max commits to walk for per-file history pub const GIT_RAW_MAX_BYTES: usize = 100 * 1024 * 1024; // 100 MB raw download limit pub const GIT_UPLOAD_PACK_MAX_BYTES: usize = 10 * 1024 * 1024; // 10 MB upload-pack body limit // -- Webhook security -- pub const WEBHOOK_TIMESTAMP_TOLERANCE_SECS: u64 = 300; // 5 minutes // -- Collections -- pub const MAX_COLLECTIONS_PER_USER: i64 = 50; pub const MAX_ITEMS_PER_COLLECTION: i64 = 200; // -- OTA updates -- pub const OTA_PRESIGN_EXPIRY_SECS: u64 = 3600; // 1 hour // OTA management: burst 10, then 2/sec (same as API write) pub const OTA_WRITE_RATE_LIMIT_MS: u64 = 500; pub const OTA_WRITE_RATE_LIMIT_BURST: u32 = 10; // OTA public (updater check, download): burst 30, then 10/sec pub const OTA_READ_RATE_LIMIT_MS: u64 = 100; pub const OTA_READ_RATE_LIMIT_BURST: u32 = 30; // -- Build pipeline -- pub const BUILD_TIMEOUT_SECS: u64 = 1800; // 30 min pub const BUILD_MAX_LOG_BYTES: usize = 5_242_880; // 5 MB pub const BUILD_HISTORY_LIMIT: i64 = 50; pub const BUILD_TRIGGER_RATE_LIMIT_PER_SEC: u64 = 1; pub const BUILD_TRIGGER_RATE_LIMIT_BURST: u32 = 3; pub const BUILD_WRITE_RATE_LIMIT_MS: u64 = 500; pub const BUILD_WRITE_RATE_LIMIT_BURST: u32 = 10; // Git browsing: burst 30, then 5/sec (blame/log can be expensive) pub const GIT_BROWSE_RATE_LIMIT_MS: u64 = 200; pub const GIT_BROWSE_RATE_LIMIT_BURST: u32 = 30; pub const BUILD_ALLOWED_TARGETS: &[&str] = &[ "linux/x86_64", "linux/aarch64", "darwin/x86_64", "darwin/aarch64", ]; // -- Streaming -- pub const STREAMING_CACHE_MAX_SECS: u64 = 86400; // 24 hours max presigned URL lifetime /// Rate limit for stream/download URL requests: 1 per 3 seconds, burst of 10. pub const STREAM_RATE_LIMIT_MS: u64 = 3000; pub const STREAM_RATE_LIMIT_BURST: u32 = 10; // -- Date display formats -- pub const DATE_FMT_SHORT: &str = "%b %d"; // "Mar 25" pub const DATE_FMT_FULL: &str = "%b %d, %Y"; // "Mar 25, 2026" pub const DATE_FMT_ISO: &str = "%Y-%m-%d"; // "2026-03-25" pub const DATE_FMT_DATETIME: &str = "%b %d, %Y %H:%M"; // "Mar 25, 2026 14:30" pub const DATE_FMT_DATETIME_UTC: &str = "%b %d, %Y %H:%M UTC"; // "Mar 25, 2026 14:30 UTC" // -- Platform content -- pub const CHANGELOG_PROJECT_SLUG: &str = "changelog"; // -- String / buffer limits -- pub const USER_AGENT_MAX_LENGTH: usize = 512; pub const SYNCKIT_MAX_KEY_ENVELOPE_BYTES: usize = 4096; pub const MAX_PRICE_CENTS: i32 = 1_000_000; // $10,000 // -- Sandbox accounts -- /// How long a sandbox session lasts before auto-cleanup. pub const SANDBOX_EXPIRY_SECS: i64 = 3600; // 1 hour /// How often the cleanup job runs. pub const SANDBOX_CLEANUP_INTERVAL_SECS: u64 = 300; // 5 minutes /// Rate limit: sandbox creation. /// Production: 1 per 30 seconds, burst 2. fast-tests: 1 per 10ms, burst 10. /// Run integration tests with `cargo test --features fast-tests` to avoid rate-limit failures. #[cfg(not(feature = "fast-tests"))] pub const SANDBOX_RATE_LIMIT_MS: u64 = 30_000; #[cfg(not(feature = "fast-tests"))] pub const SANDBOX_RATE_LIMIT_BURST: u32 = 2; #[cfg(feature = "fast-tests")] pub const SANDBOX_RATE_LIMIT_MS: u64 = 10; #[cfg(feature = "fast-tests")] pub const SANDBOX_RATE_LIMIT_BURST: u32 = 10; /// Max concurrent active sandboxes per IP. pub const SANDBOX_MAX_PER_IP: i64 = 3; #[cfg(test)] mod tests { use super::*; // -- Price constants -- #[test] fn max_price_cents_is_positive() { assert!(MAX_PRICE_CENTS > 0); } #[test] fn max_price_cents_sane_upper_bound() { // Should not exceed $100,000 assert!(MAX_PRICE_CENTS <= 10_000_000); } #[test] fn min_subscription_price_positive() { assert!(MIN_SUBSCRIPTION_PRICE_CENTS > 0); } #[test] fn min_subscription_price_below_max() { assert!(MIN_SUBSCRIPTION_PRICE_CENTS < MAX_PRICE_CENTS); } // -- Stripe fee constants -- #[test] fn stripe_fee_percentage_reasonable() { assert!(STRIPE_FEE_PERCENTAGE > 0.0); assert!(STRIPE_FEE_PERCENTAGE < 0.5); // less than 50% } #[test] fn stripe_fee_fixed_positive() { assert!(STRIPE_FEE_FIXED_CENTS > 0.0); } // -- Database pool -- #[test] fn db_pool_max_exceeds_min() { assert!(DB_POOL_MAX_CONNECTIONS > DB_POOL_MIN_CONNECTIONS); } #[test] fn db_pool_min_positive() { assert!(DB_POOL_MIN_CONNECTIONS > 0); } #[test] fn db_acquire_timeout_positive() { assert!(DB_ACQUIRE_TIMEOUT_SECS > 0); } #[test] fn db_max_lifetime_exceeds_idle_timeout() { assert!(DB_MAX_LIFETIME_SECS > DB_IDLE_TIMEOUT_SECS); } // -- Session constants -- #[test] fn session_expiry_positive() { assert!(SESSION_EXPIRY_DAYS > 0); } #[test] fn session_expiry_not_absurd() { assert!(SESSION_EXPIRY_DAYS <= 365); } #[test] fn session_touch_cache_positive() { assert!(SESSION_TOUCH_CACHE_SECS > 0); } #[test] fn session_touch_cache_less_than_one_day() { assert!(SESSION_TOUCH_CACHE_SECS < 86400); } // -- Login security -- #[test] fn max_login_attempts_positive() { assert!(MAX_LOGIN_ATTEMPTS > 0); } #[test] fn lockout_minutes_positive() { assert!(LOCKOUT_MINUTES > 0); } // -- Email link expiry ordering -- #[test] fn password_reset_expiry_positive() { assert!(PASSWORD_RESET_EXPIRY_SECS > 0); } #[test] fn email_verification_longer_than_password_reset() { assert!(EMAIL_VERIFICATION_EXPIRY_SECS > PASSWORD_RESET_EXPIRY_SECS); } #[test] fn account_deletion_expiry_positive() { assert!(ACCOUNT_DELETION_EXPIRY_SECS > 0); } // -- Scheduler -- #[test] fn scheduler_interval_positive() { assert!(SCHEDULER_INTERVAL_SECS > 0); } // -- Rate limit bursts all positive -- #[test] fn auth_rate_limit_burst_positive() { assert!(AUTH_RATE_LIMIT_BURST > 0); } #[test] fn validate_rate_limit_burst_positive() { assert!(VALIDATE_RATE_LIMIT_BURST > 0); } #[test] fn api_write_rate_limit_burst_positive() { assert!(API_WRITE_RATE_LIMIT_BURST > 0); } #[test] fn api_read_rate_limit_burst_positive() { assert!(API_READ_RATE_LIMIT_BURST > 0); } #[test] fn api_export_rate_limit_burst_positive() { assert!(API_EXPORT_RATE_LIMIT_BURST > 0); } #[test] fn license_key_rate_limit_burst_positive() { assert!(LICENSE_KEY_RATE_LIMIT_BURST > 0); } #[test] fn upload_rate_limit_burst_positive() { assert!(UPLOAD_RATE_LIMIT_BURST > 0); } #[test] fn oauth_rate_limit_burst_positive() { assert!(OAUTH_RATE_LIMIT_BURST > 0); } #[test] fn oauth_token_rate_limit_burst_positive() { assert!(OAUTH_TOKEN_RATE_LIMIT_BURST > 0); } // -- Rate limit burst ordering: read > write > auth -- #[test] fn api_read_burst_exceeds_write_burst() { assert!(API_READ_RATE_LIMIT_BURST > API_WRITE_RATE_LIMIT_BURST); } #[test] fn api_write_burst_exceeds_auth_burst() { assert!(API_WRITE_RATE_LIMIT_BURST > AUTH_RATE_LIMIT_BURST); } // -- Rate limit intervals positive -- #[test] fn auth_rate_limit_ms_positive() { assert!(AUTH_RATE_LIMIT_MS > 0); } #[test] fn api_write_rate_limit_ms_positive() { assert!(API_WRITE_RATE_LIMIT_MS > 0); } #[test] fn api_read_rate_limit_ms_positive() { assert!(API_READ_RATE_LIMIT_MS > 0); } // -- File size limits -- #[test] fn scan_max_memory_positive() { assert!(SCAN_MAX_MEMORY_BYTES > 0); } #[test] fn scan_spool_reserve_below_max() { assert!(SCAN_SPOOL_FREE_RESERVE_BYTES < SCAN_SPOOL_MAX_BYTES); } #[test] fn scan_spool_max_exceeds_memory_threshold() { assert!(SCAN_SPOOL_MAX_BYTES > SCAN_MAX_MEMORY_BYTES as u64); } #[test] fn scan_job_retention_days_safe_floor() { // Guards against an accidental same-day purge that would race the // worker stamping completed_at. assert!(SCAN_JOB_RETENTION_DAYS >= 7); } #[test] fn broadcast_parallelism_sane() { assert!(BROADCAST_PARALLELISM > 0 && BROADCAST_PARALLELISM <= 64); } #[test] fn scan_zip_max_uncompressed_exceeds_memory_threshold() { assert!(SCAN_ZIP_MAX_UNCOMPRESSED > SCAN_MAX_MEMORY_BYTES as u64); } #[test] fn git_raw_max_exceeds_file_display_limit() { assert!(GIT_RAW_MAX_BYTES > GIT_MAX_FILE_SIZE_BYTES); } #[test] fn scan_zip_max_ratio_positive() { assert!(SCAN_ZIP_MAX_RATIO > 0.0); } #[test] fn scan_zip_max_depth_positive() { assert!(SCAN_ZIP_MAX_DEPTH > 0); } // -- SyncKit -- #[test] fn synckit_push_max_changes_positive() { assert!(SYNCKIT_PUSH_MAX_CHANGES > 0); } #[test] fn synckit_pull_page_size_positive() { assert!(SYNCKIT_PULL_PAGE_SIZE > 0); } #[test] fn synckit_max_blob_size_positive() { assert!(SYNCKIT_MAX_BLOB_SIZE_BYTES > 0); } #[test] fn synckit_jwt_expiry_positive() { assert!(SYNCKIT_JWT_EXPIRY_SECS > 0); } // -- TOTP -- #[test] fn totp_digits_is_six() { assert_eq!(TOTP_DIGITS, 6); } #[test] fn totp_step_is_30() { assert_eq!(TOTP_STEP, 30); } #[test] fn backup_code_count_positive() { assert!(BACKUP_CODE_COUNT > 0); } #[test] fn backup_code_length_positive() { assert!(BACKUP_CODE_LENGTH > 0); } // -- Pagination -- #[test] fn discover_page_size_positive() { assert!(DISCOVER_PAGE_SIZE > 0); } #[test] fn feed_page_size_positive() { assert!(FEED_PAGE_SIZE > 0); } #[test] fn pagination_window_size_positive() { assert!(PAGINATION_WINDOW_SIZE > 0); } // -- String constants non-empty -- #[test] fn date_formats_non_empty() { assert!(!DATE_FMT_SHORT.is_empty()); assert!(!DATE_FMT_FULL.is_empty()); assert!(!DATE_FMT_ISO.is_empty()); assert!(!DATE_FMT_DATETIME.is_empty()); assert!(!DATE_FMT_DATETIME_UTC.is_empty()); } #[test] fn changelog_project_slug_non_empty() { assert!(!CHANGELOG_PROJECT_SLUG.is_empty()); } #[test] fn build_allowed_targets_non_empty() { assert!(!BUILD_ALLOWED_TARGETS.is_empty()); for target in BUILD_ALLOWED_TARGETS { assert!(!target.is_empty()); assert!(target.contains('/'), "target should be os/arch format: {}", target); } } // -- Collections -- #[test] fn max_collections_per_user_positive() { assert!(MAX_COLLECTIONS_PER_USER > 0); } #[test] fn max_items_per_collection_positive() { assert!(MAX_ITEMS_PER_COLLECTION > 0); } // -- Build pipeline -- #[test] fn build_timeout_positive() { assert!(BUILD_TIMEOUT_SECS > 0); } #[test] fn build_max_log_bytes_positive() { assert!(BUILD_MAX_LOG_BYTES > 0); } // -- Health monitoring -- #[test] fn health_check_interval_positive() { assert!(HEALTH_CHECK_INTERVAL_SECS > 0); } #[test] fn alert_cooldown_exceeds_health_check() { assert!(ALERT_COOLDOWN_SECS > HEALTH_CHECK_INTERVAL_SECS); } // -- Sandbox -- #[test] fn sandbox_expiry_positive() { assert!(SANDBOX_EXPIRY_SECS > 0); } #[test] fn sandbox_cleanup_interval_positive() { assert!(SANDBOX_CLEANUP_INTERVAL_SECS > 0); } #[test] fn sandbox_cleanup_less_than_expiry() { assert!(SANDBOX_CLEANUP_INTERVAL_SECS < SANDBOX_EXPIRY_SECS as u64); } #[test] fn sandbox_max_per_ip_positive() { assert!(SANDBOX_MAX_PER_IP > 0); } // -- Webhook -- #[test] fn webhook_timestamp_tolerance_positive() { assert!(WEBHOOK_TIMESTAMP_TOLERANCE_SECS > 0); } // -- OAuth -- #[test] fn oauth_code_expiry_positive() { assert!(OAUTH_CODE_EXPIRY_SECS > 0); } #[test] fn oauth_code_length_positive() { assert!(OAUTH_CODE_LENGTH > 0); } // -- Buffer limits -- #[test] fn user_agent_max_length_positive() { assert!(USER_AGENT_MAX_LENGTH > 0); } #[test] fn synckit_max_key_envelope_bytes_positive() { assert!(SYNCKIT_MAX_KEY_ENVELOPE_BYTES > 0); } }