Skip to content

Commit eab1514

Browse files
committed
test(server+java-sdk): 后台 loop 取消退出/listenTCP TLS 分支 + TlsSocketFactory 守卫与 mTLS——server 91.0%、Java SDK 96%
1 parent 050e6b9 commit eab1514

3 files changed

Lines changed: 145 additions & 0 deletions

File tree

internal/server/control_handler_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1966,3 +1966,38 @@ func TestControlService_HandleRegisterSchemaDiffWarnings(t *testing.T) {
19661966
assert.Empty(t, warnings)
19671967
})
19681968
}
1969+
1970+
// 后台 loop 的 ctx 取消退出路径(此前 cleanupLoop 41.7% / pruneOldMetrics 71.4%):
1971+
// 预取消 ctx 后同步直调,两个 loop 应立即返回且不 panic。
1972+
func TestControlService_BackgroundLoops_ExitOnCanceledContext(t *testing.T) {
1973+
t.Run("pruneOldMetrics returns on done", func(t *testing.T) {
1974+
svc := newTestControlService()
1975+
svc.Stop() // 预取消
1976+
done := make(chan struct{})
1977+
go func() {
1978+
svc.pruneOldMetrics()
1979+
close(done)
1980+
}()
1981+
select {
1982+
case <-done:
1983+
case <-time.After(2 * time.Second):
1984+
t.Fatal("pruneOldMetrics must exit on canceled context")
1985+
}
1986+
})
1987+
1988+
t.Run("cleanupLoop returns on done and prunes expired sessions", func(t *testing.T) {
1989+
loader := &mockAgentSessionLoader{}
1990+
svc := newTestControlServiceWithLoader(loader)
1991+
svc.Stop()
1992+
done := make(chan struct{})
1993+
go func() {
1994+
svc.cleanupLoop()
1995+
close(done)
1996+
}()
1997+
select {
1998+
case <-done:
1999+
case <-time.After(2 * time.Second):
2000+
t.Fatal("cleanupLoop must exit on canceled context")
2001+
}
2002+
})
2003+
}

internal/server/tcp_listener_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,16 @@ package server
22

33
import (
44
"context"
5+
"crypto/rand"
6+
"crypto/rsa"
7+
"crypto/x509"
8+
"crypto/x509/pkix"
9+
"encoding/pem"
510
"log/slog"
11+
"math/big"
612
"net"
13+
"os"
14+
"path/filepath"
715
"testing"
816
"time"
917

@@ -936,3 +944,64 @@ func (c *countingClusterHooks) OnAgentHeartbeat(ctx context.Context, agentID str
936944
c.heartbeats++
937945
}
938946
func (c *countingClusterHooks) OnAgentDisconnected(ctx context.Context, agentID string) {}
947+
948+
// listenTCP TLS 分支:cert/key 正常加载、坏路径/坏 CA 报错(此前 63.2%)。
949+
func TestListenTCP_TLSBranches(t *testing.T) {
950+
dir := t.TempDir()
951+
certPath := filepath.Join(dir, "cert.pem")
952+
keyPath := filepath.Join(dir, "key.pem")
953+
caPath := filepath.Join(dir, "ca.pem")
954+
955+
// 生成自签证书(crypto/x509 标准库,无外部依赖)
956+
priv, err := rsa.GenerateKey(rand.Reader, 2048)
957+
require.NoError(t, err)
958+
tmpl := x509.Certificate{
959+
SerialNumber: big.NewInt(1),
960+
Subject: pkix.Name{CommonName: "localhost"},
961+
NotBefore: time.Now().Add(-time.Hour),
962+
NotAfter: time.Now().Add(time.Hour),
963+
IsCA: true,
964+
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
965+
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
966+
}
967+
der, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, &priv.PublicKey, priv)
968+
require.NoError(t, err)
969+
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
970+
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
971+
require.NoError(t, os.WriteFile(certPath, certPEM, 0o600))
972+
require.NoError(t, os.WriteFile(keyPath, keyPEM, 0o600))
973+
require.NoError(t, os.WriteFile(caPath, certPEM, 0o600))
974+
975+
// cert+key+CA 齐备:TLS listener 建立成功
976+
ln, err := listenTCP(&TCPListenerConfig{
977+
Address: "127.0.0.1:0",
978+
Insecure: false,
979+
CertFile: certPath, KeyFile: keyPath, CAFile: caPath,
980+
})
981+
require.NoError(t, err)
982+
ln.Close()
983+
984+
// 坏证书路径 → load server certificate 错误
985+
_, err = listenTCP(&TCPListenerConfig{
986+
Address: "127.0.0.1:0", Insecure: false,
987+
CertFile: filepath.Join(dir, "missing.pem"), KeyFile: keyPath,
988+
})
989+
require.ErrorContains(t, err, "load server certificate")
990+
991+
// 坏 CA 文件路径 → read CA file 错误
992+
_, err = listenTCP(&TCPListenerConfig{
993+
Address: "127.0.0.1:0", Insecure: false,
994+
CertFile: certPath, KeyFile: keyPath,
995+
CAFile: filepath.Join(dir, "missing-ca.pem"),
996+
})
997+
require.ErrorContains(t, err, "read CA file")
998+
999+
// 非 PEM 内容 → append CA certificate 错误
1000+
badCA := filepath.Join(dir, "bad-ca.pem")
1001+
require.NoError(t, os.WriteFile(badCA, []byte("not a pem"), 0o600))
1002+
_, err = listenTCP(&TCPListenerConfig{
1003+
Address: "127.0.0.1:0", Insecure: false,
1004+
CertFile: certPath, KeyFile: keyPath, CAFile: badCA,
1005+
})
1006+
require.ErrorContains(t, err, "append CA certificate")
1007+
}

sdks/java/src/test/java/io/github/cuihairu/croupier/sdk/transport/TlsTransportTest.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,45 @@ void handshakeAndEndpointVerification() throws Exception {
123123
serverSocket.close();
124124
pool.shutdownNow();
125125
}
126+
@Test
127+
@Timeout(60)
128+
@DisplayName("TlsSocketFactory 守卫分支:缺 caFile / 坏 PEM / mTLS 上下文构建")
129+
void factoryGuardsAndMutualTls() throws Exception {
130+
Path dir = Files.createTempDirectory("croupier-tls-mtls");
131+
Path cert = generateCert(dir, "srv", null, null);
132+
133+
// 缺 caFile → IOException
134+
assertThrows(java.io.IOException.class,
135+
() -> TlsSocketFactory.create(null, null, null));
136+
assertThrows(java.io.IOException.class,
137+
() -> TlsSocketFactory.create(" ", null, null));
138+
139+
// 坏 CA 内容(非 PEM)→ 无证书可解析
140+
Path badCa = dir.resolve("bad-ca.pem");
141+
Files.writeString(badCa, "not a pem document");
142+
assertThrows(Exception.class, () -> TlsSocketFactory.create(badCa.toString(), null, null));
143+
144+
// mTLS:程序化生成客户端 RSA 私钥(PKCS#8 PEM)+ 复用服务端证书作链
145+
java.security.KeyPairGenerator kpg = java.security.KeyPairGenerator.getInstance("RSA");
146+
kpg.initialize(2048);
147+
java.security.KeyPair kp = kpg.generateKeyPair();
148+
String pkcs8Pem = "-----BEGIN PRIVATE KEY-----\n"
149+
+ java.util.Base64.getMimeEncoder().encodeToString(kp.getPrivate().getEncoded())
150+
+ "\n-----END PRIVATE KEY-----\n";
151+
Path clientKey = dir.resolve("client.key");
152+
Files.writeString(clientKey, pkcs8Pem);
153+
154+
// certFile 存在但 keyFile 为空 → 不启用 mTLS,仅 CA 校验
155+
assertNotNull(TlsSocketFactory.create(cert.toString(), cert.toString(), null));
156+
157+
// keyFile 内容坏 → 私钥解析失败
158+
Path badKey = dir.resolve("bad.key");
159+
Files.writeString(badKey, "-----BEGIN PRIVATE KEY-----\nbm90IGEga2V5\n-----END PRIVATE KEY-----\n");
160+
assertThrows(Exception.class,
161+
() -> TlsSocketFactory.create(cert.toString(), cert.toString(), badKey.toString()));
162+
163+
// 完整 mTLS 上下文构建成功
164+
SSLSocketFactory mtlsFactory = TlsSocketFactory.create(cert.toString(), cert.toString(), clientKey.toString());
165+
assertNotNull(mtlsFactory);
166+
}
126167
}

0 commit comments

Comments
 (0)