apollo-router 2.14.0-rc.2

A configurable, high-performance routing runtime for Apollo Federation 🚀
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
//! Tests for the export module

use std::fs;
use std::net::SocketAddr;
use std::path::Path;
use std::path::PathBuf;
use std::str::FromStr;

use async_compression::tokio::bufread::GzipDecoder;
use bytes::Bytes;
use futures::StreamExt;
use futures::TryStreamExt;
use tempfile::tempdir;
use tokio::io::BufReader;
use tokio_tar::Archive;

use super::*;

/// Helper function to collect streaming archive data into a Vec<u8>
async fn collect_streaming_archive(
    config: &Config,
    supergraph_schema: &str,
    router_config: &serde_json::Value,
) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
    let stream = Exporter::create_streaming_archive(
        config.clone(),
        Arc::new(supergraph_schema.to_string()),
        Arc::from(router_config.to_string()),
    )
    .await;

    let chunks: Vec<Bytes> = stream
        .map(|chunk| chunk.expect("Stream should not fail"))
        .collect()
        .await;

    Ok(chunks.into_iter().fold(Vec::new(), |mut acc, chunk| {
        acc.extend_from_slice(&chunk);
        acc
    }))
}

#[tokio::test]
async fn test_export_service_creation() {
    let config = Config {
        enabled: true,
        listen: SocketAddr::from_str("127.0.0.1:8089").unwrap().into(),
        output_directory: PathBuf::from("/tmp/router-diagnostics"),
    };

    let test_config_json = Arc::new(serde_json::json!({
        "test": "configuration"
    }));
    let export_service = Exporter::new(
        config.clone(),
        Arc::new("supergraph_schema".to_string()),
        Arc::from(test_config_json.to_string()),
    );
    assert_eq!(
        export_service.config().output_directory,
        config.output_directory
    );
}

#[tokio::test]
async fn test_create_archive() {
    // Create a temporary directory for test files
    let temp_dir = tempdir().expect("Failed to create temp dir");
    let output_path = temp_dir.path();

    // On Linux, create memory files; on other platforms, the directory will be handled by the archive function
    #[cfg(target_family = "unix")]
    {
        let memory_path = format!("{}/memory", output_path.to_string_lossy());
        fs::create_dir_all(&memory_path).expect("Failed to create memory directory");
        fs::write(
            format!("{}/test_heap.prof", memory_path),
            b"test heap dump data",
        )
        .expect("Failed to write test file");
        fs::write(
            format!("{}/test_profile.prof", memory_path),
            b"test profile data",
        )
        .expect("Failed to write another test file");
    }

    let config = Config {
        enabled: true,
        listen: SocketAddr::from_str("127.0.0.1:8089").unwrap().into(),
        output_directory: output_path.to_path_buf(),
    };

    // Create the archive with test configuration
    let test_full_config = serde_json::json!({
        "experimental_diagnostics": {
            "enabled": true,
            "shared_secret": "test-secret"
        }
    });
    let archive_data =
        collect_streaming_archive(&config, "test supergraph schema content", &test_full_config)
            .await
            .expect("Archive creation should succeed");
    assert!(!archive_data.is_empty(), "Archive should not be empty");

    // Verify the archive can be decompressed and read
    let reader = BufReader::new(archive_data.as_slice());
    let decoder = GzipDecoder::new(reader);
    let mut archive = Archive::new(decoder);

    let mut entries = archive
        .entries()
        .expect("Should be able to read archive entries");
    let mut found_manifest = false;
    let mut found_router_yaml = false;
    let mut found_supergraph = false;
    let mut found_memory_content = false;
    // Router binary no longer included - enhanced profiles have embedded symbols
    let mut found_system_info = false;
    let mut found_html_report = false;

    while let Some(entry) = entries.try_next().await.expect("Should read entry") {
        let path = entry
            .path()
            .expect("Should have path")
            .to_string_lossy()
            .to_string();

        match path.as_str() {
            "manifest.txt" => found_manifest = true,
            "router.yaml" => found_router_yaml = true,
            "supergraph.graphql" => found_supergraph = true,
            "system_info.txt" => found_system_info = true,
            "diagnostics_report.html" => found_html_report = true,
            // Router binary no longer included
            path if path.starts_with("memory/") => found_memory_content = true,
            _ => {} // Other files are okay
        }
    }

    assert!(found_manifest, "Archive should contain manifest.txt");
    assert!(found_router_yaml, "Archive should contain router.yaml");
    assert!(
        found_supergraph,
        "Archive should contain supergraph.graphql"
    );
    assert!(found_system_info, "Archive should contain system_info.txt");
    assert!(
        found_memory_content,
        "Archive should contain memory/ content (files on Linux, README on other platforms)"
    );
    assert!(
        found_html_report,
        "Archive should contain diagnostics_report.html"
    );
    // Router binary no longer included - enhanced profiles have embedded symbols
}

#[tokio::test]
async fn test_create_main_manifest() {
    let config = Config {
        enabled: true,
        listen: SocketAddr::from_str("127.0.0.1:8089").unwrap().into(),
        output_directory: PathBuf::from("/tmp/test-diagnostics"),
    };

    let result = Exporter::create_main_manifest(&config);
    let manifest_str = result.expect("Manifest creation should succeed");

    // Check that manifest contains expected content
    assert!(
        manifest_str.contains("APOLLO ROUTER DIAGNOSTIC ARCHIVE"),
        "Should contain title"
    );
    assert!(
        manifest_str.contains("Router Version:"),
        "Should contain version info"
    );
    assert!(
        manifest_str.contains(&format!("Platform: {}", std::env::consts::OS)),
        "Should contain current platform info"
    );
    assert!(
        manifest_str.contains("Memory Output Directory: /tmp/test-diagnostics"),
        "Should contain output directory"
    );
    assert!(
        manifest_str.contains("supergraph.graphql"),
        "Should mention supergraph.graphql file"
    );
    assert!(
        manifest_str.contains("system_info.txt"),
        "Should mention system_info.txt file"
    );
    assert!(
        manifest_str.contains("memory/"),
        "Should mention memory directory or info"
    );
    assert!(
        manifest_str.contains("diagnostics_report.html"),
        "Should mention diagnostics_report.html"
    );

    // Platform-specific checks
    #[cfg(target_family = "unix")]
    {
        assert!(
            manifest_str.contains("Memory Profiling: Enabled"),
            "Should mention memory profiling enabled on Linux"
        );
        assert!(
            manifest_str.contains("jemalloc profiling"),
            "Should mention jemalloc on Linux"
        );
    }
    #[cfg(not(target_family = "unix"))]
    {
        assert!(
            manifest_str.contains("Memory Profiling: Not available"),
            "Should mention memory profiling not available on non-Linux"
        );
        assert!(
            manifest_str.contains("Heap dumps require Linux platform"),
            "Should mention Linux requirement on non-Linux platforms"
        );
    }
}

// Test for router binary removed - enhanced profiles now have embedded symbols

#[cfg(target_family = "unix")]
#[tokio::test]
async fn test_archive_with_empty_output_directory() {
    // Use a non-existent directory
    let config = Config {
        enabled: true,
        listen: SocketAddr::from_str("127.0.0.1:8089").unwrap().into(),

        output_directory: PathBuf::from("/tmp/nonexistent-diagnostics-dir"),
    };

    // Ensure the directory doesn't exist
    if Path::new(&config.output_directory).exists() {
        fs::remove_dir_all(&config.output_directory).ok();
    }

    let test_full_config = serde_json::json!({"test": "config"});
    let archive_data = collect_streaming_archive(&config, "supergraph_schema", &test_full_config)
        .await
        .expect("Archive creation should succeed even with empty output directory");
    assert!(!archive_data.is_empty(), "Archive should not be empty");

    // Verify the archive still contains expected structure
    let reader = BufReader::new(archive_data.as_slice());
    let decoder = GzipDecoder::new(reader);
    let mut archive = Archive::new(decoder);

    let mut entries = archive
        .entries()
        .expect("Should be able to read archive entries");
    let mut found_manifest = false;
    let mut found_memory_dir = false;

    while let Some(entry) = entries
        .try_next()
        .await
        .expect("Should be able to read entry")
    {
        let path = entry
            .path()
            .expect("Should have path")
            .to_string_lossy()
            .to_string();

        match path.as_str() {
            "manifest.txt" => found_manifest = true,
            "memory/" => found_memory_dir = true, // Empty directory should still be created
            _ => {}                               // Other files are okay
        }
    }

    assert!(found_manifest, "Archive should contain manifest.txt");
    assert!(
        found_memory_dir,
        "Archive should contain empty memory/ directory"
    );
}

#[tokio::test]
async fn test_create_archive_sync() {
    // Test for archive creation - works on all platforms
    let temp_dir = tempdir().expect("Failed to create temp dir");
    let output_path = temp_dir.path();

    let config = Config {
        enabled: true,
        listen: SocketAddr::from_str("127.0.0.1:8089").unwrap().into(),
        output_directory: output_path.to_path_buf(),
    };

    let test_full_config = serde_json::json!({"test": "config"});
    let archive_data = collect_streaming_archive(&config, "supergraph_schema", &test_full_config)
        .await
        .expect("Archive creation should succeed");
    assert!(!archive_data.is_empty(), "Archive should not be empty");
    assert!(
        archive_data.len() > 100,
        "Archive should have substantial content"
    );
}

#[cfg(target_family = "unix")]
#[tokio::test]
async fn test_tar_gz_format_compatibility() {
    // This test verifies that our archives are proper tar.gz format
    // that can be extracted with standard tools
    let temp_dir = tempdir().expect("Failed to create temp dir");
    let output_path = temp_dir.path();

    // Create test files in memory subdirectory
    let memory_path = format!("{}/memory", output_path.to_string_lossy());
    fs::create_dir_all(&memory_path).expect("Failed to create memory directory");
    fs::write(
        format!("{}/heap_dump_1.prof", memory_path),
        b"heap dump content 1",
    )
    .expect("Failed to write test file");
    fs::write(
        format!("{}/heap_dump_2.prof", memory_path),
        b"heap dump content 2",
    )
    .expect("Failed to write test file");

    let config = Config {
        enabled: true,
        listen: SocketAddr::from_str("127.0.0.1:8089").unwrap().into(),
        output_directory: output_path.to_path_buf(),
    };

    // Create the archive with test configuration
    let test_full_config = serde_json::json!({
        "server": {
            "listen": "127.0.0.1:4000"
        },
        "experimental_diagnostics": {
            "enabled": true,
            "shared_secret": "test-secret"
        }
    });
    let archive_data = collect_streaming_archive(&config, "supergraph_schema", &test_full_config)
        .await
        .expect("Archive creation should succeed");

    // Create extraction directory
    let extract_dir = tempdir().expect("Failed to create extraction dir");
    let extract_path = extract_dir.path();

    // Write archive to file and extract it using standard tar/gzip tools
    let archive_file = extract_path.join("test-archive.tar.gz");
    fs::write(&archive_file, &archive_data).expect("Failed to write archive file");

    // Extract using tar::Archive (simulating standard tar.gz extraction)
    let file = tokio::fs::File::open(&archive_file)
        .await
        .expect("Failed to open archive file");
    let reader = BufReader::new(file);
    let decoder = GzipDecoder::new(reader);
    let mut archive = Archive::new(decoder);

    // Extract all files
    archive
        .unpack(extract_path)
        .await
        .expect("Failed to extract archive");

    // Verify extracted contents
    let manifest_path = extract_path.join("manifest.txt");
    assert!(manifest_path.exists(), "Extracted manifest should exist");
    let manifest_content = fs::read_to_string(&manifest_path).expect("Failed to read manifest");
    assert!(
        manifest_content.contains("APOLLO ROUTER DIAGNOSTIC ARCHIVE"),
        "Manifest should have correct content"
    );

    // Verify router.yaml was extracted and contains expected content
    let router_yaml_path = extract_path.join("router.yaml");
    assert!(
        router_yaml_path.exists(),
        "Extracted router.yaml should exist"
    );
    let router_yaml_content =
        fs::read_to_string(&router_yaml_path).expect("Failed to read router.yaml");
    assert!(
        router_yaml_content.contains("server"),
        "router.yaml should contain configuration data"
    );
    assert!(
        router_yaml_content.contains("experimental_diagnostics"),
        "router.yaml should contain diagnostics config"
    );

    // Verify supergraph.graphql was extracted and contains expected content
    let supergraph_path = extract_path.join("supergraph.graphql");
    assert!(
        supergraph_path.exists(),
        "Extracted supergraph.graphql should exist"
    );
    let supergraph_content =
        fs::read_to_string(&supergraph_path).expect("Failed to read supergraph.graphql");
    assert_eq!(
        supergraph_content, "supergraph_schema",
        "supergraph.graphql should contain the schema content"
    );

    let memory_dir = extract_path.join("memory");
    assert!(
        memory_dir.exists(),
        "Extracted memory directory should exist"
    );

    let heap_dump_1 = memory_dir.join("heap_dump_1.prof");
    assert!(heap_dump_1.exists(), "First heap dump should exist");
    let content_1 = fs::read(&heap_dump_1).expect("Failed to read heap dump 1");
    assert_eq!(
        content_1, b"heap dump content 1",
        "Heap dump content should match"
    );

    let heap_dump_2 = memory_dir.join("heap_dump_2.prof");
    assert!(heap_dump_2.exists(), "Second heap dump should exist");
    let content_2 = fs::read(&heap_dump_2).expect("Failed to read heap dump 2");
    assert_eq!(
        content_2, b"heap dump content 2",
        "Heap dump content should match"
    );

    // Router binary no longer included - enhanced profiles have embedded symbols

    // Verify that the archive is a valid gzip file by checking magic bytes
    assert!(
        archive_data.len() > 2,
        "Archive should be large enough for headers"
    );
    assert_eq!(
        &archive_data[0..2],
        &[0x1f, 0x8b],
        "Should have gzip magic bytes"
    );
}

#[cfg(target_family = "unix")]
#[tokio::test]
async fn test_manual_archive_inspection() {
    // Manual test to debug archive issues - outputs to /tmp for inspection
    let temp_dir = tempdir().expect("Failed to create temp dir");
    let output_path = temp_dir.path();

    // Create some test data
    let memory_path = format!("{}/memory", output_path.to_string_lossy());
    fs::create_dir_all(&memory_path).expect("Failed to create memory directory");
    fs::write(format!("{}/test.prof", memory_path), b"test profile data")
        .expect("Failed to write test file");

    let config = Config {
        enabled: true,
        listen: SocketAddr::from_str("127.0.0.1:8089").unwrap().into(),
        output_directory: output_path.to_path_buf(),
    };

    let test_full_config = serde_json::json!({
        "server": {"listen": "127.0.0.1:4000"},
        "experimental_diagnostics": {"enabled": true, "shared_secret": "test-secret"}
    });
    let archive_data = collect_streaming_archive(&config, "supergraph_schema", &test_full_config)
        .await
        .expect("Archive creation should succeed");

    // Write to /tmp for manual inspection
    let debug_archive = "/tmp/debug_router_diagnostics.tar.gz";
    fs::write(debug_archive, &archive_data).expect("Failed to write debug archive");
    tracing::debug!("Debug archive written to: {}", debug_archive);

    // Verify with multiple tools
    let tar_list = std::process::Command::new("tar")
        .args(["-tzf", debug_archive])
        .output()
        .expect("Failed to run tar");

    tracing::debug!(
        "Tar list output: {}",
        String::from_utf8_lossy(&tar_list.stdout)
    );
    tracing::debug!(
        "Tar list stderr: {}",
        String::from_utf8_lossy(&tar_list.stderr)
    );
    assert!(tar_list.status.success(), "tar -t should work");

    // Try to extract to verify completeness
    let extract_dir = "/tmp/debug_extract";
    let _ = fs::remove_dir_all(extract_dir); // Clean up any previous run
    fs::create_dir_all(extract_dir).expect("Failed to create extract dir");

    let tar_extract = std::process::Command::new("tar")
        .args(["-xzf", debug_archive, "-C", extract_dir])
        .output()
        .expect("Failed to run tar extract");

    tracing::debug!(
        "Tar extract stderr: {}",
        String::from_utf8_lossy(&tar_extract.stderr)
    );
    assert!(tar_extract.status.success(), "tar -x should work");

    // Verify extracted files exist
    assert!(Path::new(&format!("{}/manifest.txt", extract_dir)).exists());
    assert!(Path::new(&format!("{}/router.yaml", extract_dir)).exists());
    assert!(Path::new(&format!("{}/memory", extract_dir)).exists());

    // Log router.yaml content for verification (debug only)
    let router_yaml_content = fs::read_to_string(format!("{extract_dir}/router.yaml"))
        .expect("Failed to read router.yaml");
    tracing::debug!("Router.yaml content:\n{}", router_yaml_content);
}

#[cfg(target_family = "unix")]
#[tokio::test]
async fn test_archive_format_with_system_tar() {
    // Test that verifies our archives work with system tar command
    let temp_dir = tempdir().expect("Failed to create temp dir");
    let output_path = temp_dir.path();

    let config = Config {
        enabled: true,
        listen: SocketAddr::from_str("127.0.0.1:8089").unwrap().into(),
        output_directory: output_path.to_path_buf(),
    };

    let test_full_config = serde_json::json!({
        "server": {"listen": "127.0.0.1:4000"},
        "experimental_diagnostics": {"enabled": true}
    });
    let archive_data = collect_streaming_archive(&config, "supergraph_schema", &test_full_config)
        .await
        .expect("Archive creation should succeed");

    // Write archive to a file and test with system tar
    let archive_file = temp_dir.path().join("test.tar.gz");
    fs::write(&archive_file, &archive_data).expect("Failed to write archive");

    // Try to list contents with tar command
    let output = std::process::Command::new("tar")
        .args(["-tzf", archive_file.to_str().unwrap()])
        .output()
        .expect("Failed to run tar command");

    assert!(
        output.status.success(),
        "tar command should succeed: stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let contents = String::from_utf8(output.stdout).expect("tar output should be valid UTF-8");
    assert!(
        contents.contains("manifest.txt"),
        "Archive should contain manifest.txt"
    );
    assert!(
        contents.contains("router.yaml"),
        "Archive should contain router.yaml"
    );
    assert!(
        contents.contains("supergraph.graphql"),
        "Archive should contain supergraph.graphql"
    );
    assert!(
        contents.contains("memory/"),
        "Archive should contain memory directory"
    );
}

#[cfg(target_family = "unix")]
#[tokio::test]
async fn test_tar_gz_structure_validation() {
    // Test that ensures our tar.gz has the correct internal structure
    let temp_dir = tempdir().expect("Failed to create temp dir");
    let output_path = temp_dir.path();

    let config = Config {
        enabled: true,
        listen: SocketAddr::from_str("127.0.0.1:8089").unwrap().into(),
        output_directory: output_path.to_path_buf(),
    };

    let test_full_config = serde_json::json!({"test": "configuration"});
    let archive_data = collect_streaming_archive(&config, "supergraph_schema", &test_full_config)
        .await
        .expect("Archive creation should succeed");

    // Parse the tar.gz structure
    let reader = BufReader::new(archive_data.as_slice());
    let decoder = GzipDecoder::new(reader);
    let mut archive = Archive::new(decoder);

    let mut entries_stream = archive.entries().expect("Should be able to read entries");

    let mut entries = Vec::new();
    while let Some(entry) = entries_stream
        .try_next()
        .await
        .expect("Should be able to read entry")
    {
        entries.push(entry);
    }

    // Verify minimum expected structure
    assert!(!entries.is_empty(), "Archive should contain entries");

    let paths: Vec<String> = entries
        .iter()
        .map(|entry: &tokio_tar::Entry<_>| entry.path().unwrap().to_string_lossy().to_string())
        .collect();

    // Check for required files/directories
    assert!(
        paths.contains(&"manifest.txt".to_string()),
        "Should contain manifest.txt"
    );
    assert!(
        paths.contains(&"router.yaml".to_string()),
        "Should contain router.yaml"
    );
    assert!(
        paths.contains(&"supergraph.graphql".to_string()),
        "Should contain supergraph.graphql"
    );
    assert!(
        paths
            .iter()
            .any(|p| p == "memory/" || p.starts_with("memory/")),
        "Should contain memory directory or files"
    );
    // Router binary no longer included - enhanced profiles have embedded symbols

    // Verify file metadata
    for entry in entries {
        let path = entry.path().unwrap().to_string_lossy().to_string();
        let header = entry.header();

        // Verify proper file permissions
        if path == "manifest.txt" || path.ends_with(".prof") || path.contains("router") {
            assert_ne!(
                header.mode().unwrap(),
                0,
                "Files should have proper permissions"
            );
        }

        // Verify proper entry types
        if path.ends_with("/") {
            assert!(
                header.entry_type().is_dir(),
                "Directories should be marked as directories"
            );
        } else if !header.entry_type().is_dir() {
            assert!(
                header.entry_type().is_file(),
                "Non-directories should be files"
            );
        }
    }
}

#[tokio::test]
async fn test_non_unix_memory_handling() {
    // Test specifically for non-Linux platforms to ensure they get the README file
    let temp_dir = tempdir().expect("Failed to create temp dir");
    let output_path = temp_dir.path();

    let config = Config {
        enabled: true,
        listen: SocketAddr::from_str("127.0.0.1:8089").unwrap().into(),
        output_directory: output_path.to_path_buf(),
    };

    let test_full_config = serde_json::json!({"test": "config"});
    let archive_data = collect_streaming_archive(&config, "supergraph_schema", &test_full_config)
        .await
        .expect("Archive creation should succeed");
    let reader = BufReader::new(archive_data.as_slice());
    let decoder = GzipDecoder::new(reader);
    let mut archive = Archive::new(decoder);

    let mut entries = archive
        .entries()
        .expect("Should be able to read archive entries");

    let mut found_memory_content = false;
    while let Some(entry) = entries
        .try_next()
        .await
        .expect("Should be able to read entry")
    {
        let path = entry
            .path()
            .expect("Should have path")
            .to_string_lossy()
            .to_string();

        if path.starts_with("memory/") {
            found_memory_content = true;
            #[cfg(target_family = "unix")]
            {
                // On Linux, we might have actual profile files if they exist
                assert!(
                    path.ends_with(".prof") || path == "memory/" || path == "memory/README.txt",
                    "Memory files should be profile files or directories on Linux"
                );
            }
            #[cfg(not(target_family = "unix"))]
            {
                // On non-Linux, we should only have the README
                assert_eq!(
                    path, "memory/README.txt",
                    "Non-Linux should only have README in memory directory"
                );
            }
        }
    }

    assert!(
        found_memory_content,
        "Should have some memory-related content"
    );
}

#[tokio::test]
async fn test_system_info_collection() {
    // Test that system information collection works on all platforms
    let result = super::super::system_info::collect().await;
    assert!(result.is_ok(), "System info collection should succeed");

    let system_info = result.unwrap();
    assert!(!system_info.is_empty(), "System info should not be empty");

    // Check for required sections that should exist on all platforms
    assert!(
        system_info.contains("SYSTEM INFORMATION"),
        "Should contain system information header"
    );
    assert!(
        system_info.contains("Operating System:"),
        "Should contain OS information"
    );
    assert!(
        system_info.contains("Architecture:"),
        "Should contain architecture information"
    );
    assert!(
        system_info.contains("Router Version:"),
        "Should contain router version"
    );
    assert!(
        system_info.contains("MEMORY INFORMATION"),
        "Should contain memory information section"
    );
    assert!(
        system_info.contains("CPU INFORMATION"),
        "Should contain CPU information section"
    );
    assert!(
        system_info.contains("Physical CPU cores:"),
        "Should contain CPU core count"
    );
    assert!(
        system_info.contains("RELEVANT ENVIRONMENT VARIABLES"),
        "Should contain environment variables section"
    );

    // Platform-specific checks
    #[cfg(target_family = "unix")]
    {
        // On Linux, we might have detailed memory and CPU info
        // But we don't assert on these as they depend on system state
        // Just verify the structure is there
        assert!(
            system_info.contains("------------------"),
            "Should have section separators"
        );
    }

    #[cfg(not(target_family = "unix"))]
    {
        // On non-Linux platforms, we should have fallback messages
        assert!(
            system_info.contains("not available") || system_info.contains("Unknown"),
            "Should have appropriate fallback messages for unavailable info"
        );
    }
}

#[tokio::test]
async fn test_system_info_in_archive_extraction() {
    // Test that system info is properly included in archive and can be extracted
    let temp_dir = tempdir().expect("Failed to create temp dir");
    let output_path = temp_dir.path();

    let config = Config {
        enabled: true,
        listen: SocketAddr::from_str("127.0.0.1:8089").unwrap().into(),
        output_directory: output_path.to_path_buf(),
    };

    let test_full_config = serde_json::json!({"test": "config"});
    let archive_data = collect_streaming_archive(&config, "supergraph_schema", &test_full_config)
        .await
        .expect("Archive creation should succeed");

    // Extract and verify system_info.txt
    let extract_dir = tempdir().expect("Failed to create extraction dir");
    let extract_path = extract_dir.path();

    let reader = BufReader::new(archive_data.as_slice());
    let decoder = GzipDecoder::new(reader);
    let mut archive = Archive::new(decoder);

    archive
        .unpack(extract_path)
        .await
        .expect("Failed to extract archive");

    // Verify system_info.txt was extracted and contains expected content
    let system_info_path = extract_path.join("system_info.txt");
    assert!(
        system_info_path.exists(),
        "Extracted system_info.txt should exist"
    );

    let system_info_content =
        fs::read_to_string(&system_info_path).expect("Failed to read system_info.txt");

    // Verify basic structure exists
    assert!(
        system_info_content.contains("SYSTEM INFORMATION"),
        "system_info.txt should contain system information header"
    );
    assert!(
        system_info_content.contains(&format!("Operating System: {}", std::env::consts::OS)),
        "system_info.txt should contain correct OS information"
    );
    assert!(
        system_info_content.contains(&format!("Architecture: {}", std::env::consts::ARCH)),
        "system_info.txt should contain correct architecture information"
    );

    // The content should be substantial (more than just headers)
    assert!(
        system_info_content.len() > 500,
        "system_info.txt should contain substantial information"
    );
}

#[tokio::test]
async fn test_system_info_environment_variables() {
    // Test that environment variable collection works properly
    // This test only runs when APOLLO_GRAPH_REF is set (e.g., in CI)
    if std::env::var("APOLLO_GRAPH_REF").is_ok() {
        let system_info = super::super::system_info::collect()
            .await
            .expect("Should collect system info");

        // Should have environment variables section
        assert!(
            system_info.contains("RELEVANT ENVIRONMENT VARIABLES"),
            "Should have environment variables section"
        );

        // Should contain the APOLLO_GRAPH_REF since it's set
        assert!(
            system_info.contains("APOLLO_GRAPH_REF:"),
            "Should contain APOLLO_GRAPH_REF when it's set"
        );

        // Should not contain the "no variables" message
        assert!(
            !system_info.contains("No relevant Apollo environment variables set"),
            "Should not show 'no variables' message when APOLLO_GRAPH_REF is set"
        );
    } else {
        // When APOLLO_GRAPH_REF is not set, test the fallback behavior
        let system_info = super::super::system_info::collect()
            .await
            .expect("Should collect system info");

        // Should have environment variables section
        assert!(
            system_info.contains("RELEVANT ENVIRONMENT VARIABLES"),
            "Should have environment variables section"
        );

        // Should indicate no relevant variables are set
        assert!(
            system_info.contains("No relevant Apollo environment variables set"),
            "Should indicate no relevant variables when APOLLO_GRAPH_REF is not set"
        );
    }
}

#[tokio::test]
async fn test_system_info_cpu_count() {
    // Test that CPU count detection works on all platforms
    let system_info = super::super::system_info::collect()
        .await
        .expect("Should collect system info");

    // Should contain CPU information
    assert!(
        system_info.contains("Physical CPU cores:"),
        "Should contain CPU core count information"
    );

    // The actual number should be at least 1
    let cpu_count = std::thread::available_parallelism()
        .map(|p| p.get())
        .unwrap_or(1);
    assert!(cpu_count >= 1, "Should detect at least 1 CPU core");

    // Verify the detected count appears in the system info
    assert!(
        system_info.contains(&cpu_count.to_string()),
        "System info should contain the detected CPU count"
    );
}

#[tokio::test]
async fn test_html_report_in_archive() {
    // Test that diagnostics_report.html is included in the archive
    let temp_dir = tempdir().expect("Failed to create temp dir");
    let output_path = temp_dir.path();

    let config = Config {
        enabled: true,
        listen: SocketAddr::from_str("127.0.0.1:8089").unwrap().into(),
        output_directory: output_path.to_path_buf(),
    };

    let test_full_config = serde_json::json!({"test": "config"});
    let archive_data = collect_streaming_archive(&config, "supergraph_schema", &test_full_config)
        .await
        .expect("Archive creation should succeed");

    // Parse the archive to check for HTML report
    let reader = BufReader::new(archive_data.as_slice());
    let decoder = GzipDecoder::new(reader);
    let mut archive = Archive::new(decoder);

    let mut entries_stream = archive.entries().expect("Should be able to read entries");
    let mut found_html_report = false;

    while let Some(entry) = entries_stream
        .try_next()
        .await
        .expect("Should be able to read entry")
    {
        let path = entry.path().unwrap().to_string_lossy().to_string();
        if path == "diagnostics_report.html" {
            found_html_report = true;
            let header = entry.header();

            // Verify it's a file
            assert!(
                header.entry_type().is_file(),
                "diagnostics_report.html should be a regular file"
            );

            // Verify it has content (HTML should be substantial)
            assert!(
                header.size().unwrap() > 1000,
                "diagnostics_report.html should have substantial content"
            );
        }
    }

    assert!(
        found_html_report,
        "Archive should contain diagnostics_report.html"
    );
}