mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-27 07:16:19 +09:00
e6a6dbe3f6
* feat(channel): add qq adapter and outbound delivery * feat(channel): ingest inbound qq messages * feat(web): expose qq channel in management ui * feat(channel): support qq attachment ingestion * fix(mcp): fail read raw immediately for missing files * fix(agent): parse inline image data into native image parts * test(agent): align read_media tool tests with SDK options * fix(channel): harden qq image delivery and reconnect loop Avoid data URLs for qq channel images, reset reconnect backoff after healthy sessions, and fall back gracefully for malformed public image URLs. * fix(channel): restore qq media delivery and target resolution * fix(qq,mcp,agent): fix message/qq regressions and pass go lint * fix(qq,agent): validate inline base64 and sync heartbeat seq * fix(qq): validate remote voice mime for upload checks * fix(qq): fall back intents and restore adapter wiring * fix(qq): prevent final text leakage and dedupe persisted inbound query
131 lines
3.1 KiB
Go
131 lines
3.1 KiB
Go
package mcpclient
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"net"
|
|
"testing"
|
|
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/credentials/insecure"
|
|
"google.golang.org/grpc/status"
|
|
"google.golang.org/grpc/test/bufconn"
|
|
|
|
pb "github.com/memohai/memoh/internal/mcp/mcpcontainer"
|
|
)
|
|
|
|
const testBufSize = 1 << 20
|
|
|
|
type rawReadTestServer struct {
|
|
pb.UnimplementedContainerServiceServer
|
|
files map[string][]byte
|
|
}
|
|
|
|
func (s *rawReadTestServer) ReadRaw(req *pb.ReadRawRequest, stream pb.ContainerService_ReadRawServer) error {
|
|
data, ok := s.files[req.GetPath()]
|
|
if !ok {
|
|
return status.Errorf(codes.NotFound, "open: open %s: no such file or directory", req.GetPath())
|
|
}
|
|
if len(data) == 0 {
|
|
return nil
|
|
}
|
|
if err := stream.Send(&pb.DataChunk{Data: data[:1]}); err != nil {
|
|
return err
|
|
}
|
|
if len(data) > 1 {
|
|
if err := stream.Send(&pb.DataChunk{Data: data[1:]}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func newTestReadRawClient(t *testing.T, files map[string][]byte) *Client {
|
|
t.Helper()
|
|
|
|
lis := bufconn.Listen(testBufSize)
|
|
srv := grpc.NewServer()
|
|
pb.RegisterContainerServiceServer(srv, &rawReadTestServer{files: files})
|
|
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
_ = srv.Serve(lis)
|
|
}()
|
|
t.Cleanup(func() {
|
|
srv.Stop()
|
|
<-done
|
|
})
|
|
|
|
dialer := func(ctx context.Context, _ string) (net.Conn, error) {
|
|
return lis.DialContext(ctx)
|
|
}
|
|
conn, err := grpc.NewClient("passthrough://bufnet",
|
|
grpc.WithContextDialer(dialer),
|
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("grpc.NewClient: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = conn.Close() })
|
|
|
|
return NewClientFromConn(conn)
|
|
}
|
|
|
|
func TestClientReadRawMissingFileReturnsNotFoundImmediately(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client := newTestReadRawClient(t, map[string][]byte{})
|
|
_, err := client.ReadRaw(context.Background(), "/data/media/missing.jpg")
|
|
if err == nil {
|
|
t.Fatal("expected read raw to fail for missing file")
|
|
}
|
|
if !errors.Is(err, ErrNotFound) {
|
|
t.Fatalf("expected ErrNotFound, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestClientReadRawPreservesFirstChunk(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client := newTestReadRawClient(t, map[string][]byte{
|
|
"/data/media/existing.jpg": []byte("hello"),
|
|
})
|
|
reader, err := client.ReadRaw(context.Background(), "/data/media/existing.jpg")
|
|
if err != nil {
|
|
t.Fatalf("ReadRaw returned error: %v", err)
|
|
}
|
|
defer func() { _ = reader.Close() }()
|
|
|
|
data, err := io.ReadAll(reader)
|
|
if err != nil {
|
|
t.Fatalf("read raw reader failed: %v", err)
|
|
}
|
|
if got := string(data); got != "hello" {
|
|
t.Fatalf("expected full payload, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestClientReadRawSupportsEmptyFile(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client := newTestReadRawClient(t, map[string][]byte{
|
|
"/data/media/empty.txt": {},
|
|
})
|
|
reader, err := client.ReadRaw(context.Background(), "/data/media/empty.txt")
|
|
if err != nil {
|
|
t.Fatalf("ReadRaw returned error: %v", err)
|
|
}
|
|
defer func() { _ = reader.Close() }()
|
|
|
|
data, err := io.ReadAll(reader)
|
|
if err != nil {
|
|
t.Fatalf("read raw empty reader failed: %v", err)
|
|
}
|
|
if len(data) != 0 {
|
|
t.Fatalf("expected empty payload, got %q", string(data))
|
|
}
|
|
}
|