|
1 | 1 | package copilot |
2 | 2 |
|
3 | 3 | import ( |
4 | | - "bufio" |
5 | 4 | "encoding/json" |
6 | | - "fmt" |
7 | | - "io" |
8 | 5 | "os" |
9 | 6 | "path/filepath" |
10 | 7 | "reflect" |
11 | 8 | "regexp" |
12 | 9 | "sync" |
13 | 10 | "testing" |
14 | | - |
15 | | - "github.com/github/copilot-sdk/go/internal/jsonrpc2" |
16 | 11 | ) |
17 | 12 |
|
18 | 13 | // This file is for unit tests. Where relevant, prefer to add e2e tests in e2e/*.test.go instead |
@@ -574,230 +569,3 @@ func TestClient_StartStopRace(t *testing.T) { |
574 | 569 | t.Fatal(err) |
575 | 570 | } |
576 | 571 | } |
577 | | - |
578 | | -// fakeJSONRPCServer reads one JSON-RPC request from r and sends a response to w. |
579 | | -// onRequest is called with the parsed method and params before the response is sent, |
580 | | -// allowing the caller to inspect state (e.g. the sessions map) during the RPC. |
581 | | -func fakeJSONRPCServer(t *testing.T, r io.Reader, w io.WriteCloser, onRequest func(method string, params json.RawMessage)) { |
582 | | - t.Helper() |
583 | | - reader := bufio.NewReader(r) |
584 | | - |
585 | | - // Read Content-Length header |
586 | | - var contentLength int |
587 | | - for { |
588 | | - line, err := reader.ReadString('\n') |
589 | | - if err != nil { |
590 | | - t.Errorf("failed to read header: %v", err) |
591 | | - w.Close() |
592 | | - return |
593 | | - } |
594 | | - if line == "\r\n" || line == "\n" { |
595 | | - break |
596 | | - } |
597 | | - fmt.Sscanf(line, "Content-Length: %d", &contentLength) |
598 | | - } |
599 | | - |
600 | | - // Read body |
601 | | - body := make([]byte, contentLength) |
602 | | - if _, err := io.ReadFull(reader, body); err != nil { |
603 | | - t.Errorf("failed to read body: %v", err) |
604 | | - w.Close() |
605 | | - return |
606 | | - } |
607 | | - |
608 | | - // Parse request |
609 | | - var req struct { |
610 | | - ID json.RawMessage `json:"id"` |
611 | | - Method string `json:"method"` |
612 | | - Params json.RawMessage `json:"params"` |
613 | | - } |
614 | | - if err := json.Unmarshal(body, &req); err != nil { |
615 | | - t.Errorf("failed to unmarshal request: %v", err) |
616 | | - w.Close() |
617 | | - return |
618 | | - } |
619 | | - |
620 | | - onRequest(req.Method, req.Params) |
621 | | - |
622 | | - // Echo sessionId from request params |
623 | | - var params struct { |
624 | | - SessionID string `json:"sessionId"` |
625 | | - } |
626 | | - json.Unmarshal(req.Params, ¶ms) |
627 | | - |
628 | | - result, _ := json.Marshal(map[string]any{"sessionId": params.SessionID, "workspacePath": "/tmp"}) |
629 | | - resp, _ := json.Marshal(map[string]any{ |
630 | | - "jsonrpc": "2.0", |
631 | | - "id": req.ID, |
632 | | - "result": json.RawMessage(result), |
633 | | - }) |
634 | | - header := fmt.Sprintf("Content-Length: %d\r\n\r\n", len(resp)) |
635 | | - w.Write([]byte(header)) |
636 | | - w.Write(resp) |
637 | | -} |
638 | | - |
639 | | -// fakeJSONRPCErrorServer reads one JSON-RPC request and returns an error response. |
640 | | -func fakeJSONRPCErrorServer(t *testing.T, r io.Reader, w io.WriteCloser) { |
641 | | - t.Helper() |
642 | | - reader := bufio.NewReader(r) |
643 | | - |
644 | | - var contentLength int |
645 | | - for { |
646 | | - line, err := reader.ReadString('\n') |
647 | | - if err != nil { |
648 | | - w.Close() |
649 | | - return |
650 | | - } |
651 | | - if line == "\r\n" || line == "\n" { |
652 | | - break |
653 | | - } |
654 | | - fmt.Sscanf(line, "Content-Length: %d", &contentLength) |
655 | | - } |
656 | | - |
657 | | - body := make([]byte, contentLength) |
658 | | - if _, err := io.ReadFull(reader, body); err != nil { |
659 | | - w.Close() |
660 | | - return |
661 | | - } |
662 | | - |
663 | | - var req struct { |
664 | | - ID json.RawMessage `json:"id"` |
665 | | - } |
666 | | - json.Unmarshal(body, &req) |
667 | | - |
668 | | - resp, _ := json.Marshal(map[string]any{ |
669 | | - "jsonrpc": "2.0", |
670 | | - "id": req.ID, |
671 | | - "error": map[string]any{"code": -32000, "message": "test error"}, |
672 | | - }) |
673 | | - header := fmt.Sprintf("Content-Length: %d\r\n\r\n", len(resp)) |
674 | | - w.Write([]byte(header)) |
675 | | - w.Write(resp) |
676 | | -} |
677 | | - |
678 | | -// newTestClientWithFakeServer creates a Client wired to a fake jsonrpc2.Client |
679 | | -// backed by the provided io pipes. The caller must call jrpcClient.Stop() when done. |
680 | | -func newTestClientWithFakeServer(clientWriter io.WriteCloser, clientReader io.ReadCloser) (*Client, *jsonrpc2.Client) { |
681 | | - jrpcClient := jsonrpc2.NewClient(clientWriter, clientReader) |
682 | | - jrpcClient.Start() |
683 | | - |
684 | | - client := NewClient(nil) |
685 | | - client.client = jrpcClient |
686 | | - client.state = StateConnected |
687 | | - client.sessions = make(map[string]*Session) |
688 | | - return client, jrpcClient |
689 | | -} |
690 | | - |
691 | | -func TestClient_CreateSession_RegistersSessionBeforeRPC(t *testing.T) { |
692 | | - // Create pipes: client writes to serverReader, server writes to clientReader |
693 | | - serverReader, clientWriter := io.Pipe() |
694 | | - clientReader, serverWriter := io.Pipe() |
695 | | - client, jrpcClient := newTestClientWithFakeServer(clientWriter, clientReader) |
696 | | - defer jrpcClient.Stop() |
697 | | - |
698 | | - sessionInMap := false |
699 | | - go fakeJSONRPCServer(t, serverReader, serverWriter, func(method string, params json.RawMessage) { |
700 | | - if method != "session.create" { |
701 | | - t.Errorf("expected session.create, got %s", method) |
702 | | - } |
703 | | - var p struct { |
704 | | - SessionID string `json:"sessionId"` |
705 | | - } |
706 | | - json.Unmarshal(params, &p) |
707 | | - client.sessionsMux.Lock() |
708 | | - _, sessionInMap = client.sessions[p.SessionID] |
709 | | - client.sessionsMux.Unlock() |
710 | | - }) |
711 | | - |
712 | | - session, err := client.CreateSession(t.Context(), &SessionConfig{ |
713 | | - OnPermissionRequest: PermissionHandler.ApproveAll, |
714 | | - }) |
715 | | - if err != nil { |
716 | | - t.Fatalf("CreateSession failed: %v", err) |
717 | | - } |
718 | | - if session == nil { |
719 | | - t.Fatal("expected non-nil session") |
720 | | - } |
721 | | - if !sessionInMap { |
722 | | - t.Error("session was not in sessions map when session.create RPC was issued") |
723 | | - } |
724 | | -} |
725 | | - |
726 | | -func TestClient_ResumeSession_RegistersSessionBeforeRPC(t *testing.T) { |
727 | | - serverReader, clientWriter := io.Pipe() |
728 | | - clientReader, serverWriter := io.Pipe() |
729 | | - client, jrpcClient := newTestClientWithFakeServer(clientWriter, clientReader) |
730 | | - defer jrpcClient.Stop() |
731 | | - |
732 | | - sessionInMap := false |
733 | | - go fakeJSONRPCServer(t, serverReader, serverWriter, func(method string, params json.RawMessage) { |
734 | | - if method != "session.resume" { |
735 | | - t.Errorf("expected session.resume, got %s", method) |
736 | | - } |
737 | | - var p struct { |
738 | | - SessionID string `json:"sessionId"` |
739 | | - } |
740 | | - json.Unmarshal(params, &p) |
741 | | - client.sessionsMux.Lock() |
742 | | - _, sessionInMap = client.sessions[p.SessionID] |
743 | | - client.sessionsMux.Unlock() |
744 | | - }) |
745 | | - |
746 | | - session, err := client.ResumeSessionWithOptions(t.Context(), "test-session-id", &ResumeSessionConfig{ |
747 | | - OnPermissionRequest: PermissionHandler.ApproveAll, |
748 | | - }) |
749 | | - if err != nil { |
750 | | - t.Fatalf("ResumeSessionWithOptions failed: %v", err) |
751 | | - } |
752 | | - if session == nil { |
753 | | - t.Fatal("expected non-nil session") |
754 | | - } |
755 | | - if !sessionInMap { |
756 | | - t.Error("session was not in sessions map when session.resume RPC was issued") |
757 | | - } |
758 | | -} |
759 | | - |
760 | | -func TestClient_CreateSession_CleansUpOnRPCFailure(t *testing.T) { |
761 | | - serverReader, clientWriter := io.Pipe() |
762 | | - clientReader, serverWriter := io.Pipe() |
763 | | - client, jrpcClient := newTestClientWithFakeServer(clientWriter, clientReader) |
764 | | - defer jrpcClient.Stop() |
765 | | - |
766 | | - // Send a JSON-RPC error response to simulate failure |
767 | | - go fakeJSONRPCErrorServer(t, serverReader, serverWriter) |
768 | | - |
769 | | - _, err := client.CreateSession(t.Context(), &SessionConfig{ |
770 | | - OnPermissionRequest: PermissionHandler.ApproveAll, |
771 | | - }) |
772 | | - if err == nil { |
773 | | - t.Fatal("expected error from CreateSession") |
774 | | - } |
775 | | - client.sessionsMux.Lock() |
776 | | - count := len(client.sessions) |
777 | | - client.sessionsMux.Unlock() |
778 | | - if count != 0 { |
779 | | - t.Errorf("expected 0 sessions after failed create, got %d", count) |
780 | | - } |
781 | | -} |
782 | | - |
783 | | -func TestClient_ResumeSession_CleansUpOnRPCFailure(t *testing.T) { |
784 | | - serverReader, clientWriter := io.Pipe() |
785 | | - clientReader, serverWriter := io.Pipe() |
786 | | - client, jrpcClient := newTestClientWithFakeServer(clientWriter, clientReader) |
787 | | - defer jrpcClient.Stop() |
788 | | - |
789 | | - go fakeJSONRPCErrorServer(t, serverReader, serverWriter) |
790 | | - |
791 | | - _, err := client.ResumeSessionWithOptions(t.Context(), "test-session-id", &ResumeSessionConfig{ |
792 | | - OnPermissionRequest: PermissionHandler.ApproveAll, |
793 | | - }) |
794 | | - if err == nil { |
795 | | - t.Fatal("expected error from ResumeSessionWithOptions") |
796 | | - } |
797 | | - client.sessionsMux.Lock() |
798 | | - count := len(client.sessions) |
799 | | - client.sessionsMux.Unlock() |
800 | | - if count != 0 { |
801 | | - t.Errorf("expected 0 sessions after failed resume, got %d", count) |
802 | | - } |
803 | | -} |
0 commit comments