|
|
@@ -476,3 +476,569 @@ func minForMessagesTest(a, b int) int {
|
|
476
|
476
|
}
|
|
477
|
477
|
return b
|
|
478
|
478
|
}
|
|
|
479
|
+
|
|
|
480
|
+// extractTimestamp 从SessionMessage提取时间戳
|
|
|
481
|
+func extractTimestamp(msg opencode.SessionMessage) (time.Time, error) {
|
|
|
482
|
+ // 尝试从info.time.created提取
|
|
|
483
|
+ if msg.Info != nil {
|
|
|
484
|
+ // 调试:打印info的所有键
|
|
|
485
|
+ // fmt.Printf("Info keys: %v\n", getMapKeys(msg.Info))
|
|
|
486
|
+
|
|
|
487
|
+ // 1. 尝试从time.created提取(毫秒时间戳)
|
|
|
488
|
+ if timeMap, ok := msg.Info["time"].(map[string]interface{}); ok {
|
|
|
489
|
+ // fmt.Printf("Time map keys: %v\n", getMapKeys(timeMap))
|
|
|
490
|
+
|
|
|
491
|
+ // 尝试毫秒时间戳(整数)
|
|
|
492
|
+ if createdMs, ok := timeMap["created"].(float64); ok {
|
|
|
493
|
+ // 转换为秒(除以1000)
|
|
|
494
|
+ seconds := int64(createdMs) / 1000
|
|
|
495
|
+ nanoseconds := (int64(createdMs) % 1000) * 1000000
|
|
|
496
|
+ return time.Unix(seconds, nanoseconds), nil
|
|
|
497
|
+ }
|
|
|
498
|
+ // 尝试字符串格式
|
|
|
499
|
+ if createdStr, ok := timeMap["created"].(string); ok {
|
|
|
500
|
+ return time.Parse(time.RFC3339, createdStr)
|
|
|
501
|
+ }
|
|
|
502
|
+ }
|
|
|
503
|
+ // 2. 尝试直接提取created(毫秒时间戳)
|
|
|
504
|
+ if createdMs, ok := msg.Info["created"].(float64); ok {
|
|
|
505
|
+ seconds := int64(createdMs) / 1000
|
|
|
506
|
+ nanoseconds := (int64(createdMs) % 1000) * 1000000
|
|
|
507
|
+ return time.Unix(seconds, nanoseconds), nil
|
|
|
508
|
+ }
|
|
|
509
|
+ // 3. 尝试从created_at提取(字符串或数字)
|
|
|
510
|
+ if createdVal, ok := msg.Info["created_at"]; ok {
|
|
|
511
|
+ switch v := createdVal.(type) {
|
|
|
512
|
+ case float64:
|
|
|
513
|
+ seconds := int64(v) / 1000
|
|
|
514
|
+ nanoseconds := (int64(v) % 1000) * 1000000
|
|
|
515
|
+ return time.Unix(seconds, nanoseconds), nil
|
|
|
516
|
+ case string:
|
|
|
517
|
+ return time.Parse(time.RFC3339, v)
|
|
|
518
|
+ }
|
|
|
519
|
+ }
|
|
|
520
|
+ // 4. 尝试从createdAt提取(驼峰式)
|
|
|
521
|
+ if createdAtVal, ok := msg.Info["createdAt"]; ok {
|
|
|
522
|
+ switch v := createdAtVal.(type) {
|
|
|
523
|
+ case float64:
|
|
|
524
|
+ seconds := int64(v) / 1000
|
|
|
525
|
+ nanoseconds := (int64(v) % 1000) * 1000000
|
|
|
526
|
+ return time.Unix(seconds, nanoseconds), nil
|
|
|
527
|
+ case string:
|
|
|
528
|
+ return time.Parse(time.RFC3339, v)
|
|
|
529
|
+ }
|
|
|
530
|
+ }
|
|
|
531
|
+ // 5. 尝试从timestamp提取
|
|
|
532
|
+ if timestampFloat, ok := msg.Info["timestamp"].(float64); ok {
|
|
|
533
|
+ return time.Unix(int64(timestampFloat), 0), nil
|
|
|
534
|
+ }
|
|
|
535
|
+ // 6. 尝试从date提取
|
|
|
536
|
+ if dateStr, ok := msg.Info["date"].(string); ok {
|
|
|
537
|
+ return time.Parse(time.RFC3339, dateStr)
|
|
|
538
|
+ }
|
|
|
539
|
+ // 7. 尝试从time直接提取(可能是字符串)
|
|
|
540
|
+ if timeStr, ok := msg.Info["time"].(string); ok {
|
|
|
541
|
+ return time.Parse(time.RFC3339, timeStr)
|
|
|
542
|
+ }
|
|
|
543
|
+ }
|
|
|
544
|
+ return time.Time{}, fmt.Errorf("无法提取时间戳")
|
|
|
545
|
+}
|
|
|
546
|
+
|
|
|
547
|
+// getMapKeys 获取map的所有键(用于调试)
|
|
|
548
|
+func getMapKeys(m map[string]interface{}) []string {
|
|
|
549
|
+ keys := make([]string, 0, len(m))
|
|
|
550
|
+ for k := range m {
|
|
|
551
|
+ keys = append(keys, k)
|
|
|
552
|
+ }
|
|
|
553
|
+ return keys
|
|
|
554
|
+}
|
|
|
555
|
+
|
|
|
556
|
+// TestMessageFormatAndSorting 测试消息格式和排序规则
|
|
|
557
|
+func TestMessageFormatAndSorting(t *testing.T) {
|
|
|
558
|
+ // 外部已启动的OpenCode服务端口
|
|
|
559
|
+ externalOpenCodePort := 8787
|
|
|
560
|
+ opencodeURL := fmt.Sprintf("http://127.0.0.1:%d", externalOpenCodePort)
|
|
|
561
|
+
|
|
|
562
|
+ // 检查OpenCode服务是否运行
|
|
|
563
|
+ if !isServiceRunningForMessages(t, opencodeURL) {
|
|
|
564
|
+ t.Skipf("OpenCode服务未运行在 %s,跳过测试", opencodeURL)
|
|
|
565
|
+ }
|
|
|
566
|
+
|
|
|
567
|
+ t.Logf("🚀 开始测试消息格式和排序规则")
|
|
|
568
|
+ t.Logf("OpenCode URL: %s", opencodeURL)
|
|
|
569
|
+
|
|
|
570
|
+ // 使用现有的测试会话ID
|
|
|
571
|
+ sessionID := "ses_3aa9a94dfffeLIdWcLHCG97m7z"
|
|
|
572
|
+ t.Logf("测试会话ID: %s", sessionID)
|
|
|
573
|
+
|
|
|
574
|
+ // 创建客户端
|
|
|
575
|
+ client, err := opencode.NewDirectClient(externalOpenCodePort)
|
|
|
576
|
+ if err != nil {
|
|
|
577
|
+ t.Fatalf("❌ 创建OpenCode客户端失败: %v", err)
|
|
|
578
|
+ }
|
|
|
579
|
+
|
|
|
580
|
+ ctx := context.Background()
|
|
|
581
|
+
|
|
|
582
|
+ // 获取消息(不带limit)
|
|
|
583
|
+ t.Log("📝 获取消息进行格式和排序分析")
|
|
|
584
|
+ messages, err := client.GetSessionMessages(ctx, sessionID, 0)
|
|
|
585
|
+ if err != nil {
|
|
|
586
|
+ t.Fatalf("❌ 获取会话消息失败: %v", err)
|
|
|
587
|
+ }
|
|
|
588
|
+
|
|
|
589
|
+ t.Logf("✅ 获取到 %d 条消息进行分析", len(messages))
|
|
|
590
|
+
|
|
|
591
|
+ if len(messages) == 0 {
|
|
|
592
|
+ t.Skip("会话中没有消息,跳过格式和排序测试")
|
|
|
593
|
+ }
|
|
|
594
|
+
|
|
|
595
|
+ // 1. 验证消息格式完整性
|
|
|
596
|
+ t.Run("MessageFormatIntegrity", func(t *testing.T) {
|
|
|
597
|
+ for i, msg := range messages {
|
|
|
598
|
+ // 检查消息ID
|
|
|
599
|
+ msgID := extractMessageID(msg)
|
|
|
600
|
+ if msgID == "" {
|
|
|
601
|
+ t.Errorf("❌ 消息[%d]缺少ID", i)
|
|
|
602
|
+ }
|
|
|
603
|
+
|
|
|
604
|
+ // 检查角色
|
|
|
605
|
+ role := extractMessageRole(msg)
|
|
|
606
|
+ if role == "" {
|
|
|
607
|
+ t.Errorf("❌ 消息[%d]缺少角色", i)
|
|
|
608
|
+ }
|
|
|
609
|
+
|
|
|
610
|
+ // 检查内容
|
|
|
611
|
+ content := extractMessageContent(msg)
|
|
|
612
|
+ if content == "" && role != "system" {
|
|
|
613
|
+ t.Logf("⚠️ 消息[%d] (ID: %s, 角色: %s) 内容为空", i, msgID, role)
|
|
|
614
|
+ }
|
|
|
615
|
+
|
|
|
616
|
+ // 检查时间戳
|
|
|
617
|
+ timestamp, err := extractTimestamp(msg)
|
|
|
618
|
+ if err != nil {
|
|
|
619
|
+ t.Logf("⚠️ 消息[%d] (ID: %s) 无法提取时间戳: %v", i, msgID, err)
|
|
|
620
|
+ } else {
|
|
|
621
|
+ t.Logf(" 消息[%d] 时间戳: %v", i, timestamp.Format("2006-01-02 15:04:05"))
|
|
|
622
|
+ }
|
|
|
623
|
+ }
|
|
|
624
|
+ })
|
|
|
625
|
+
|
|
|
626
|
+ // 2. 验证消息排序(时间倒序:最新在前)
|
|
|
627
|
+ t.Run("MessageSortingOrder", func(t *testing.T) {
|
|
|
628
|
+ if len(messages) < 2 {
|
|
|
629
|
+ t.Skip("消息数量不足,跳过排序测试")
|
|
|
630
|
+ }
|
|
|
631
|
+
|
|
|
632
|
+ // 收集所有有效的时间戳
|
|
|
633
|
+ var timestamps []time.Time
|
|
|
634
|
+ var validMessages []opencode.SessionMessage
|
|
|
635
|
+
|
|
|
636
|
+ for i, msg := range messages {
|
|
|
637
|
+ timestamp, err := extractTimestamp(msg)
|
|
|
638
|
+ if err == nil {
|
|
|
639
|
+ timestamps = append(timestamps, timestamp)
|
|
|
640
|
+ validMessages = append(validMessages, msg)
|
|
|
641
|
+ } else {
|
|
|
642
|
+ t.Logf("⚠️ 消息[%d] 跳过排序检查(无有效时间戳)", i)
|
|
|
643
|
+ }
|
|
|
644
|
+ }
|
|
|
645
|
+
|
|
|
646
|
+ if len(timestamps) < 2 {
|
|
|
647
|
+ t.Skip("有效时间戳数量不足,跳过排序测试")
|
|
|
648
|
+ }
|
|
|
649
|
+
|
|
|
650
|
+ // 检查排序方向
|
|
|
651
|
+ // 先确定排序方向:检查前几对消息
|
|
|
652
|
+ var isDescending bool
|
|
|
653
|
+ if len(timestamps) >= 2 {
|
|
|
654
|
+ // 检查前几对确定方向
|
|
|
655
|
+ descendingCount := 0
|
|
|
656
|
+ ascendingCount := 0
|
|
|
657
|
+ for i := 1; i < minForMessagesTest(5, len(timestamps)); i++ {
|
|
|
658
|
+ if timestamps[i].Before(timestamps[i-1]) {
|
|
|
659
|
+ descendingCount++ // 时间递减(最新在前)
|
|
|
660
|
+ } else if timestamps[i].After(timestamps[i-1]) {
|
|
|
661
|
+ ascendingCount++ // 时间递增(最早在前)
|
|
|
662
|
+ }
|
|
|
663
|
+ }
|
|
|
664
|
+
|
|
|
665
|
+ if descendingCount > ascendingCount {
|
|
|
666
|
+ isDescending = true
|
|
|
667
|
+ t.Logf("✅ 消息按时间倒序排列(最新在前)")
|
|
|
668
|
+ } else if ascendingCount > descendingCount {
|
|
|
669
|
+ isDescending = false
|
|
|
670
|
+ t.Logf("✅ 消息按时间顺序排列(最早在前)")
|
|
|
671
|
+ } else {
|
|
|
672
|
+ t.Logf("⚠️ 无法确定排序方向,可能时间戳相同或乱序")
|
|
|
673
|
+ isDescending = false
|
|
|
674
|
+ }
|
|
|
675
|
+
|
|
|
676
|
+ // 验证排序一致性
|
|
|
677
|
+ if isDescending {
|
|
|
678
|
+ // 应该时间递减
|
|
|
679
|
+ for i := 1; i < len(timestamps); i++ {
|
|
|
680
|
+ if timestamps[i].After(timestamps[i-1]) {
|
|
|
681
|
+ t.Logf("❌ 倒序不一致: 消息[%d] (%v) 比消息[%d] (%v) 更晚",
|
|
|
682
|
+ i, timestamps[i].Format("15:04:05"),
|
|
|
683
|
+ i-1, timestamps[i-1].Format("15:04:05"))
|
|
|
684
|
+ }
|
|
|
685
|
+ }
|
|
|
686
|
+ } else {
|
|
|
687
|
+ // 应该时间递增
|
|
|
688
|
+ for i := 1; i < len(timestamps); i++ {
|
|
|
689
|
+ if timestamps[i].Before(timestamps[i-1]) {
|
|
|
690
|
+ t.Logf("❌ 顺序不一致: 消息[%d] (%v) 比消息[%d] (%v) 更早",
|
|
|
691
|
+ i, timestamps[i].Format("15:04:05"),
|
|
|
692
|
+ i-1, timestamps[i-1].Format("15:04:05"))
|
|
|
693
|
+ }
|
|
|
694
|
+ }
|
|
|
695
|
+ }
|
|
|
696
|
+ }
|
|
|
697
|
+
|
|
|
698
|
+ if isDescending {
|
|
|
699
|
+ t.Logf("✅ 消息按时间倒序排列(最新在前)")
|
|
|
700
|
+ // 打印时间戳范围
|
|
|
701
|
+ if len(timestamps) > 0 {
|
|
|
702
|
+ firstTime := timestamps[0]
|
|
|
703
|
+ lastTime := timestamps[len(timestamps)-1]
|
|
|
704
|
+ t.Logf("📊 时间戳范围:")
|
|
|
705
|
+ t.Logf(" 第一条消息: %v (%v)", firstTime.Format("2006-01-02 15:04:05"), firstTime.Unix())
|
|
|
706
|
+ t.Logf(" 最后一条消息: %v (%v)", lastTime.Format("2006-01-02 15:04:05"), lastTime.Unix())
|
|
|
707
|
+ t.Logf(" 时间跨度: %v", lastTime.Sub(firstTime))
|
|
|
708
|
+
|
|
|
709
|
+ // 打印前3条和后3条
|
|
|
710
|
+ t.Logf(" 前3条消息时间:")
|
|
|
711
|
+ for i := 0; i < minForMessagesTest(3, len(timestamps)); i++ {
|
|
|
712
|
+ t.Logf(" [%d]: %v", i, timestamps[i].Format("15:04:05"))
|
|
|
713
|
+ }
|
|
|
714
|
+ if len(timestamps) > 6 {
|
|
|
715
|
+ t.Logf(" 后3条消息时间:")
|
|
|
716
|
+ for i := len(timestamps) - 3; i < len(timestamps); i++ {
|
|
|
717
|
+ t.Logf(" [%d]: %v", i, timestamps[i].Format("15:04:05"))
|
|
|
718
|
+ }
|
|
|
719
|
+ }
|
|
|
720
|
+ }
|
|
|
721
|
+ }
|
|
|
722
|
+ // 不因为排序方向而失败,只是记录信息
|
|
|
723
|
+ if !isDescending {
|
|
|
724
|
+ t.Logf("ℹ️ 消息按时间顺序排列(最早在前),这对于增量加载是好事")
|
|
|
725
|
+ }
|
|
|
726
|
+ })
|
|
|
727
|
+
|
|
|
728
|
+ // 3. 验证limit参数对排序的影响
|
|
|
729
|
+ t.Run("LimitParameterEffect", func(t *testing.T) {
|
|
|
730
|
+ // 获取带limit的消息
|
|
|
731
|
+ limit := 5
|
|
|
732
|
+ if len(messages) < limit {
|
|
|
733
|
+ limit = len(messages)
|
|
|
734
|
+ }
|
|
|
735
|
+
|
|
|
736
|
+ limitedMessages, err := client.GetSessionMessages(ctx, sessionID, limit)
|
|
|
737
|
+ if err != nil {
|
|
|
738
|
+ t.Fatalf("❌ 获取限制数量的消息失败: %v", err)
|
|
|
739
|
+ }
|
|
|
740
|
+
|
|
|
741
|
+ t.Logf("✅ 获取到 %d 条限制消息", len(limitedMessages))
|
|
|
742
|
+
|
|
|
743
|
+ // 检查limit是否有效
|
|
|
744
|
+ if len(limitedMessages) > limit {
|
|
|
745
|
+ t.Errorf("❌ limit参数无效: 返回 %d 条消息,限制为 %d", len(limitedMessages), limit)
|
|
|
746
|
+ }
|
|
|
747
|
+
|
|
|
748
|
+ // 检查limit消息是否与原消息前N条一致
|
|
|
749
|
+ if len(limitedMessages) > 0 && len(messages) >= len(limitedMessages) {
|
|
|
750
|
+ for i := 0; i < len(limitedMessages); i++ {
|
|
|
751
|
+ limitedID := extractMessageID(limitedMessages[i])
|
|
|
752
|
+ fullID := extractMessageID(messages[i])
|
|
|
753
|
+
|
|
|
754
|
+ if limitedID != fullID {
|
|
|
755
|
+ t.Errorf("❌ limit消息不匹配: 位置[%d] limit消息ID=%s, 全量消息ID=%s",
|
|
|
756
|
+ i, limitedID, fullID)
|
|
|
757
|
+ break
|
|
|
758
|
+ }
|
|
|
759
|
+ }
|
|
|
760
|
+ if t.Failed() {
|
|
|
761
|
+ t.Logf("⚠️ limit消息与全量消息前N条不一致")
|
|
|
762
|
+ } else {
|
|
|
763
|
+ t.Logf("✅ limit参数正确返回前%d条消息", limit)
|
|
|
764
|
+ }
|
|
|
765
|
+ }
|
|
|
766
|
+ })
|
|
|
767
|
+
|
|
|
768
|
+ // 4. 输出消息格式摘要
|
|
|
769
|
+ t.Run("MessageFormatSummary", func(t *testing.T) {
|
|
|
770
|
+ t.Logf("📋 消息格式摘要:")
|
|
|
771
|
+ t.Logf(" 消息总数: %d", len(messages))
|
|
|
772
|
+
|
|
|
773
|
+ // 角色统计
|
|
|
774
|
+ roleCount := make(map[string]int)
|
|
|
775
|
+ for _, msg := range messages {
|
|
|
776
|
+ role := extractMessageRole(msg)
|
|
|
777
|
+ roleCount[role]++
|
|
|
778
|
+ }
|
|
|
779
|
+ for role, count := range roleCount {
|
|
|
780
|
+ t.Logf(" 角色 '%s': %d 条", role, count)
|
|
|
781
|
+ }
|
|
|
782
|
+
|
|
|
783
|
+ // 时间戳可用性统计
|
|
|
784
|
+ timestampCount := 0
|
|
|
785
|
+ for _, msg := range messages {
|
|
|
786
|
+ _, err := extractTimestamp(msg)
|
|
|
787
|
+ if err == nil {
|
|
|
788
|
+ timestampCount++
|
|
|
789
|
+ }
|
|
|
790
|
+ }
|
|
|
791
|
+ t.Logf(" 有效时间戳: %d/%d (%.1f%%)", timestampCount, len(messages),
|
|
|
792
|
+ float64(timestampCount)/float64(len(messages))*100)
|
|
|
793
|
+
|
|
|
794
|
+ // 内容长度统计
|
|
|
795
|
+ totalContentLength := 0
|
|
|
796
|
+ for _, msg := range messages {
|
|
|
797
|
+ content := extractMessageContent(msg)
|
|
|
798
|
+ totalContentLength += len(content)
|
|
|
799
|
+ }
|
|
|
800
|
+ t.Logf(" 总内容长度: %d 字符", totalContentLength)
|
|
|
801
|
+ if len(messages) > 0 {
|
|
|
802
|
+ t.Logf(" 平均内容长度: %.1f 字符", float64(totalContentLength)/float64(len(messages)))
|
|
|
803
|
+ }
|
|
|
804
|
+
|
|
|
805
|
+ // 打印第一条消息的完整结构(用于调试)
|
|
|
806
|
+ if len(messages) > 0 {
|
|
|
807
|
+ t.Logf("🔍 第一条消息完整结构:")
|
|
|
808
|
+ firstMsg := messages[0]
|
|
|
809
|
+ if firstMsg.Info != nil {
|
|
|
810
|
+ infoJSON, _ := json.MarshalIndent(firstMsg.Info, " ", " ")
|
|
|
811
|
+ t.Logf(" Info: %s", infoJSON)
|
|
|
812
|
+ // 打印Info的所有键
|
|
|
813
|
+ t.Logf(" Info keys: %v", getMapKeys(firstMsg.Info))
|
|
|
814
|
+ } else {
|
|
|
815
|
+ t.Logf(" Info: nil")
|
|
|
816
|
+ }
|
|
|
817
|
+ if len(firstMsg.Parts) > 0 {
|
|
|
818
|
+ partsJSON, _ := json.MarshalIndent(firstMsg.Parts, " ", " ")
|
|
|
819
|
+ t.Logf(" Parts: %s", partsJSON)
|
|
|
820
|
+ } else {
|
|
|
821
|
+ t.Logf(" Parts: 空数组")
|
|
|
822
|
+ }
|
|
|
823
|
+ }
|
|
|
824
|
+ })
|
|
|
825
|
+}
|
|
|
826
|
+
|
|
|
827
|
+// TestMessageQueryPerformance 测试消息查询性能
|
|
|
828
|
+func TestMessageQueryPerformance(t *testing.T) {
|
|
|
829
|
+ // 外部已启动的OpenCode服务端口
|
|
|
830
|
+ externalOpenCodePort := 8787
|
|
|
831
|
+ opencodeURL := fmt.Sprintf("http://127.0.0.1:%d", externalOpenCodePort)
|
|
|
832
|
+
|
|
|
833
|
+ // 检查OpenCode服务是否运行
|
|
|
834
|
+ if !isServiceRunningForMessages(t, opencodeURL) {
|
|
|
835
|
+ t.Skipf("OpenCode服务未运行在 %s,跳过测试", opencodeURL)
|
|
|
836
|
+ }
|
|
|
837
|
+
|
|
|
838
|
+ // 检查svc-code服务是否运行
|
|
|
839
|
+ svcCodeURL := "http://localhost:8020"
|
|
|
840
|
+ if !isServiceRunningForMessages(t, svcCodeURL) {
|
|
|
841
|
+ t.Skipf("svc-code服务未运行在 %s,跳过测试", svcCodeURL)
|
|
|
842
|
+ }
|
|
|
843
|
+
|
|
|
844
|
+ t.Logf("🚀 开始测试消息查询性能")
|
|
|
845
|
+ t.Logf("OpenCode URL: %s", opencodeURL)
|
|
|
846
|
+ t.Logf("svc-code URL: %s", svcCodeURL)
|
|
|
847
|
+
|
|
|
848
|
+ // 使用现有的测试会话ID
|
|
|
849
|
+ sessionID := "ses_3aa9a94dfffeLIdWcLHCG97m7z"
|
|
|
850
|
+ t.Logf("测试会话ID: %s", sessionID)
|
|
|
851
|
+
|
|
|
852
|
+ // 先获取总消息数,以确定合适的limit值
|
|
|
853
|
+ t.Log("📊 获取消息总数...")
|
|
|
854
|
+ client, err := opencode.NewDirectClient(externalOpenCodePort)
|
|
|
855
|
+ if err != nil {
|
|
|
856
|
+ t.Fatalf("❌ 创建OpenCode客户端失败: %v", err)
|
|
|
857
|
+ }
|
|
|
858
|
+
|
|
|
859
|
+ ctx := context.Background()
|
|
|
860
|
+ allMessages, err := client.GetSessionMessages(ctx, sessionID, 0)
|
|
|
861
|
+ if err != nil {
|
|
|
862
|
+ t.Fatalf("❌ 获取总消息失败: %v", err)
|
|
|
863
|
+ }
|
|
|
864
|
+
|
|
|
865
|
+ totalMessages := len(allMessages)
|
|
|
866
|
+ t.Logf("📊 会话总消息数: %d", totalMessages)
|
|
|
867
|
+
|
|
|
868
|
+ // 定义要测试的limit值
|
|
|
869
|
+ testLimits := []int{2, 5, 10, 20, 50, 100, 200, 300}
|
|
|
870
|
+ // 如果总消息数小于某个limit,调整测试值
|
|
|
871
|
+ var adjustedLimits []int
|
|
|
872
|
+ for _, limit := range testLimits {
|
|
|
873
|
+ if limit <= totalMessages {
|
|
|
874
|
+ adjustedLimits = append(adjustedLimits, limit)
|
|
|
875
|
+ }
|
|
|
876
|
+ }
|
|
|
877
|
+ adjustedLimits = append(adjustedLimits, 0) // 0表示无限制(全量)
|
|
|
878
|
+
|
|
|
879
|
+ t.Logf("📊 测试的limit值: %v", adjustedLimits)
|
|
|
880
|
+
|
|
|
881
|
+ // 1. 测试直接OpenCode API性能
|
|
|
882
|
+ t.Run("DirectOpenCodeAPI", func(t *testing.T) {
|
|
|
883
|
+ t.Log("📊 测试直接OpenCode API性能...")
|
|
|
884
|
+
|
|
|
885
|
+ for _, limit := range adjustedLimits {
|
|
|
886
|
+ start := time.Now()
|
|
|
887
|
+
|
|
|
888
|
+ // 构造URL
|
|
|
889
|
+ url := fmt.Sprintf("%s/session/%s/message", opencodeURL, sessionID)
|
|
|
890
|
+ if limit > 0 {
|
|
|
891
|
+ url = fmt.Sprintf("%s?limit=%d", url, limit)
|
|
|
892
|
+ }
|
|
|
893
|
+
|
|
|
894
|
+ // 发送请求
|
|
|
895
|
+ client := &http.Client{Timeout: 30 * time.Second}
|
|
|
896
|
+ req, err := http.NewRequest("GET", url, nil)
|
|
|
897
|
+ if err != nil {
|
|
|
898
|
+ t.Errorf("❌ 创建请求失败 (limit=%d): %v", limit, err)
|
|
|
899
|
+ continue
|
|
|
900
|
+ }
|
|
|
901
|
+ req.Header.Set("Accept", "application/json")
|
|
|
902
|
+
|
|
|
903
|
+ resp, err := client.Do(req)
|
|
|
904
|
+ if err != nil {
|
|
|
905
|
+ t.Errorf("❌ HTTP请求失败 (limit=%d): %v", limit, err)
|
|
|
906
|
+ continue
|
|
|
907
|
+ }
|
|
|
908
|
+
|
|
|
909
|
+ // 读取响应体(确保完全读取以测量网络传输时间)
|
|
|
910
|
+ body, err := io.ReadAll(resp.Body)
|
|
|
911
|
+ resp.Body.Close()
|
|
|
912
|
+
|
|
|
913
|
+ duration := time.Since(start)
|
|
|
914
|
+
|
|
|
915
|
+ if err != nil {
|
|
|
916
|
+ t.Errorf("❌ 读取响应体失败 (limit=%d): %v", limit, err)
|
|
|
917
|
+ continue
|
|
|
918
|
+ }
|
|
|
919
|
+
|
|
|
920
|
+ if resp.StatusCode != http.StatusOK {
|
|
|
921
|
+ t.Errorf("❌ 请求失败 (limit=%d): 状态码 %d", limit, resp.StatusCode)
|
|
|
922
|
+ continue
|
|
|
923
|
+ }
|
|
|
924
|
+
|
|
|
925
|
+ // 解析消息数量(可选)
|
|
|
926
|
+ var messages []opencode.SessionMessage
|
|
|
927
|
+ if err := json.Unmarshal(body, &messages); err != nil {
|
|
|
928
|
+ t.Logf("⚠️ 解析消息失败 (limit=%d): %v", limit, err)
|
|
|
929
|
+ }
|
|
|
930
|
+
|
|
|
931
|
+ actualCount := len(messages)
|
|
|
932
|
+ t.Logf(" limit=%d: 耗时=%v, 返回消息数=%d, 响应体大小=%d字节",
|
|
|
933
|
+ limit, duration, actualCount, len(body))
|
|
|
934
|
+ }
|
|
|
935
|
+ })
|
|
|
936
|
+
|
|
|
937
|
+ // 2. 测试通过svc-code API性能
|
|
|
938
|
+ t.Run("SvcCodeAPI", func(t *testing.T) {
|
|
|
939
|
+ t.Log("📊 测试svc-code API性能...")
|
|
|
940
|
+
|
|
|
941
|
+ // 用户登录获取token
|
|
|
942
|
+ token, err := loginAndGetTokenForMessagesTest(t, svcCodeURL)
|
|
|
943
|
+ if err != nil {
|
|
|
944
|
+ t.Fatalf("❌ 登录失败: %v", err)
|
|
|
945
|
+ }
|
|
|
946
|
+
|
|
|
947
|
+ for _, limit := range adjustedLimits {
|
|
|
948
|
+ start := time.Now()
|
|
|
949
|
+
|
|
|
950
|
+ // 构造URL
|
|
|
951
|
+ url := fmt.Sprintf("%s/api/session/messages", svcCodeURL)
|
|
|
952
|
+
|
|
|
953
|
+ // 构建请求体(POST方法)
|
|
|
954
|
+ requestBody := map[string]interface{}{
|
|
|
955
|
+ "sessionID": sessionID,
|
|
|
956
|
+ }
|
|
|
957
|
+ if limit > 0 {
|
|
|
958
|
+ requestBody["limit"] = limit
|
|
|
959
|
+ }
|
|
|
960
|
+
|
|
|
961
|
+ jsonBody, err := json.Marshal(requestBody)
|
|
|
962
|
+ if err != nil {
|
|
|
963
|
+ t.Errorf("❌ 编码请求体失败 (limit=%d): %v", limit, err)
|
|
|
964
|
+ continue
|
|
|
965
|
+ }
|
|
|
966
|
+
|
|
|
967
|
+ // 创建POST请求
|
|
|
968
|
+ req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonBody))
|
|
|
969
|
+ if err != nil {
|
|
|
970
|
+ t.Errorf("❌ 创建请求失败 (limit=%d): %v", limit, err)
|
|
|
971
|
+ continue
|
|
|
972
|
+ }
|
|
|
973
|
+
|
|
|
974
|
+ // 添加认证头和内容类型
|
|
|
975
|
+ req.Header.Set("Authorization", "Bearer "+token)
|
|
|
976
|
+ req.Header.Set("Content-Type", "application/json")
|
|
|
977
|
+ req.Header.Set("Accept", "application/json")
|
|
|
978
|
+
|
|
|
979
|
+ // 发送请求
|
|
|
980
|
+ client := &http.Client{Timeout: 30 * time.Second}
|
|
|
981
|
+ resp, err := client.Do(req)
|
|
|
982
|
+ if err != nil {
|
|
|
983
|
+ t.Errorf("❌ HTTP请求失败 (limit=%d): %v", limit, err)
|
|
|
984
|
+ continue
|
|
|
985
|
+ }
|
|
|
986
|
+
|
|
|
987
|
+ // 读取响应体
|
|
|
988
|
+ body, err := io.ReadAll(resp.Body)
|
|
|
989
|
+ resp.Body.Close()
|
|
|
990
|
+
|
|
|
991
|
+ duration := time.Since(start)
|
|
|
992
|
+
|
|
|
993
|
+ if err != nil {
|
|
|
994
|
+ t.Errorf("❌ 读取响应体失败 (limit=%d): %v", limit, err)
|
|
|
995
|
+ continue
|
|
|
996
|
+ }
|
|
|
997
|
+
|
|
|
998
|
+ if resp.StatusCode != http.StatusOK {
|
|
|
999
|
+ t.Errorf("❌ 请求失败 (limit=%d): 状态码 %d, 响应体: %s",
|
|
|
1000
|
+ limit, resp.StatusCode, string(body[:minForMessagesTest(200, len(body))]))
|
|
|
1001
|
+ continue
|
|
|
1002
|
+ }
|
|
|
1003
|
+
|
|
|
1004
|
+ // 解析响应
|
|
|
1005
|
+ var result struct {
|
|
|
1006
|
+ Success bool `json:"success"`
|
|
|
1007
|
+ Message string `json:"message"`
|
|
|
1008
|
+ Data struct {
|
|
|
1009
|
+ Messages []opencode.SessionMessage `json:"messages"`
|
|
|
1010
|
+ Count int `json:"count"`
|
|
|
1011
|
+ } `json:"data"`
|
|
|
1012
|
+ }
|
|
|
1013
|
+
|
|
|
1014
|
+ if err := json.Unmarshal(body, &result); err != nil {
|
|
|
1015
|
+ t.Errorf("❌ 解析响应失败 (limit=%d): %v, 响应体: %s",
|
|
|
1016
|
+ limit, err, string(body[:minForMessagesTest(200, len(body))]))
|
|
|
1017
|
+ continue
|
|
|
1018
|
+ }
|
|
|
1019
|
+
|
|
|
1020
|
+ if !result.Success {
|
|
|
1021
|
+ t.Errorf("❌ API调用失败 (limit=%d): %s", limit, result.Message)
|
|
|
1022
|
+ continue
|
|
|
1023
|
+ }
|
|
|
1024
|
+
|
|
|
1025
|
+ actualCount := len(result.Data.Messages)
|
|
|
1026
|
+ t.Logf(" limit=%d: 耗时=%v, 返回消息数=%d, 响应体大小=%d字节",
|
|
|
1027
|
+ limit, duration, actualCount, len(body))
|
|
|
1028
|
+ }
|
|
|
1029
|
+ })
|
|
|
1030
|
+
|
|
|
1031
|
+ // 3. 性能对比总结
|
|
|
1032
|
+ t.Run("PerformanceSummary", func(t *testing.T) {
|
|
|
1033
|
+ t.Log("📊 性能对比总结:")
|
|
|
1034
|
+ t.Log(" (具体数据见上述测试输出)")
|
|
|
1035
|
+ t.Log(" 📈 预期趋势:")
|
|
|
1036
|
+ t.Log(" 1. limit越小,响应时间越短")
|
|
|
1037
|
+ t.Log(" 2. 响应体大小与消息数量成正比")
|
|
|
1038
|
+ t.Log(" 3. svc-code API会有额外开销(认证、封装)")
|
|
|
1039
|
+ t.Log(" 💡 优化建议:")
|
|
|
1040
|
+ t.Log(" 1. 增量加载:只查询新消息(limit=新消息数量)")
|
|
|
1041
|
+ t.Log(" 2. 缓存:避免重复查询相同消息")
|
|
|
1042
|
+ t.Log(" 3. 前端过滤:即使查询全量,也可缓存过滤")
|
|
|
1043
|
+ })
|
|
|
1044
|
+}
|