1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: LicenseRef-MutuaL-1.2
4
5package main
6
7import (
8 "context"
9 "errors"
10 "runtime/debug"
11 "testing"
12
13 "git.secluded.site/cooked-mcp/internal/appconfig"
14)
15
16type fakeMCPRunner struct {
17 stdioCalls int
18 httpCalls int
19 httpAddr string
20 httpToken string
21 err error
22}
23
24func (r *fakeMCPRunner) RunStdio(context.Context) error {
25 r.stdioCalls++
26
27 return r.err
28}
29
30func (r *fakeMCPRunner) RunHTTP(_ context.Context, addr, token string) error {
31 r.httpCalls++
32 r.httpAddr = addr
33 r.httpToken = token
34
35 return r.err
36}
37
38// server.CONFIG.8
39func TestParseRunOptionsACIDServerConfig8ParsesHTTPAddr(t *testing.T) {
40 got, err := parseRunOptions([]string{"--http-addr", "127.0.0.1:9999"})
41 if err != nil {
42 t.Fatalf("parseRunOptions() error = %v", err)
43 }
44
45 if got.httpAddr != "127.0.0.1:9999" {
46 t.Fatalf("httpAddr = %q, want CLI address", got.httpAddr)
47 }
48 if got.transport != "stdio" {
49 t.Fatalf("transport = %q, want stdio", got.transport)
50 }
51}
52
53// server.TRANSPORT.4
54func TestValidateTransportACIDServerTransport4AcceptsStdio(t *testing.T) {
55 if err := validateTransport("stdio"); err != nil {
56 t.Fatalf("validateTransport() error = %v, want nil", err)
57 }
58}
59
60// server.TRANSPORT.2-2
61func TestValidateTransportACIDServerTransport2_2AcceptsHTTP(t *testing.T) {
62 if err := validateTransport("http"); err != nil {
63 t.Fatalf("validateTransport() error = %v, want nil", err)
64 }
65}
66
67// server.TRANSPORT.4
68func TestValidateTransportACIDServerTransport4RejectsUnknownTransport(t *testing.T) {
69 err := validateTransport("bogus")
70 if err == nil {
71 t.Fatal("validateTransport() error = nil, want unsupported transport error")
72 }
73 if err.Error() != `unsupported transport "bogus" (supported: stdio, http)` {
74 t.Fatalf("validateTransport() error = %q", err.Error())
75 }
76}
77
78// server.TRANSPORT.2-1
79func TestRunMCPServerACIDServerTransport2_1SelectsStdio(t *testing.T) {
80 runner := &fakeMCPRunner{}
81 err := runMCPServer(context.Background(), runner, appconfig.Config{}, "stdio")
82 if err != nil {
83 t.Fatalf("runMCPServer() error = %v", err)
84 }
85
86 if runner.stdioCalls != 1 {
87 t.Fatalf("stdio calls = %d, want 1", runner.stdioCalls)
88 }
89 if runner.httpCalls != 0 {
90 t.Fatalf("http calls = %d, want 0", runner.httpCalls)
91 }
92}
93
94// server.TRANSPORT.2-2 server.TRANSPORT.3
95func TestRunMCPServerACIDServerTransport2_2SelectsHTTP(t *testing.T) {
96 runner := &fakeMCPRunner{}
97 err := runMCPServer(context.Background(), runner, appconfig.Config{
98 HTTPAddr: "127.0.0.1:8123",
99 HTTPToken: "secret-token",
100 }, "http")
101 if err != nil {
102 t.Fatalf("runMCPServer() error = %v", err)
103 }
104
105 if runner.httpCalls != 1 {
106 t.Fatalf("http calls = %d, want 1", runner.httpCalls)
107 }
108 if runner.httpAddr != "127.0.0.1:8123" {
109 t.Fatalf("http addr = %q, want configured address", runner.httpAddr)
110 }
111 if runner.httpToken != "secret-token" {
112 t.Fatal("HTTP token was not passed to RunHTTP")
113 }
114 if runner.stdioCalls != 0 {
115 t.Fatalf("stdio calls = %d, want 0", runner.stdioCalls)
116 }
117}
118
119func TestRunMCPServerTreatsContextCancellationAsCleanShutdown(t *testing.T) {
120 runner := &fakeMCPRunner{err: context.Canceled}
121 err := runMCPServer(context.Background(), runner, appconfig.Config{}, "http")
122 if err != nil {
123 t.Fatalf("runMCPServer() error = %v, want nil", err)
124 }
125}
126
127func TestRunMCPServerReturnsTransportErrors(t *testing.T) {
128 wantErr := errors.New("transport failed")
129 runner := &fakeMCPRunner{err: wantErr}
130 err := runMCPServer(context.Background(), runner, appconfig.Config{}, "stdio")
131 if !errors.Is(err, wantErr) {
132 t.Fatalf("runMCPServer() error = %v, want %v", err, wantErr)
133 }
134}
135
136func TestVersionFromBuildInfoPrefersModuleVersion(t *testing.T) {
137 got := versionFromBuildInfo(&debug.BuildInfo{Main: debug.Module{Version: "1.2.3"}})
138 if got != "1.2.3" {
139 t.Fatalf("versionFromBuildInfo() = %q, want 1.2.3", got)
140 }
141}
142
143func TestVersionFromBuildInfoFallsBackToVCSRevision(t *testing.T) {
144 got := versionFromBuildInfo(&debug.BuildInfo{
145 Main: debug.Module{Version: "(devel)"},
146 Settings: []debug.BuildSetting{
147 {Key: "vcs.revision", Value: "abc123"},
148 {Key: "vcs.modified", Value: "true"},
149 },
150 })
151 if got != "abc123-dirty" {
152 t.Fatalf("versionFromBuildInfo() = %q, want abc123-dirty", got)
153 }
154}