The 1.4.2 extension replaced the old signed/encrypted chat gateway with an
OpenAI-compatible aigateway. Align the proxy with the new flow:
- Use ecloud.10086.cn login/model base URLs, zhanlu_ide plugin headers and
v1.4.2 plugin version
- Provision the model API key via SM2-signed get-or-create after v1/login
profile fetch; store api_key/model_base_url/email in credentials
- Chat via Bearer apiKey against {modelBaseUrl}/chat/completions with plain
OpenAI SSE passthrough; fetch /v1/models from the gateway model-info endpoint
- Force HTTP/1.1 upstream (gateway drops HTTP/2 ALPN negotiation with EOF)
- Drop obsolete AES body encryption, model name mapping and vscode headers
75 lines
2.4 KiB
Go
75 lines
2.4 KiB
Go
package sign
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.misaka.ren/M1saka/zhanlu_proxy/internal/auth"
|
|
)
|
|
|
|
const testPublicKeyPEM = `-----BEGIN PUBLIC KEY-----
|
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhxudxTewPgljUHEZHkusP7m3I+zA4/RGvuUMt6TtII/m4zwUOm/Y31zHBTmkCCt8k5vj9y+AmO0TsGmHooNQuMebakdmEWdcA5h7YAHHFbF2w5LcxIXjib08vgVpA+m3R5xPbLK+vfHe2aAX36b5nHReDNncY5vAl3U4CgIEBGPqyG67vJytRWqP+sfEdw5+m192Rf4SCGyiBzRmjiVlH3zeEBjdbOrkAnzKOVz6AHBl2q7LPLJKIzxjoAyhEp5qnDjHUFo5VZUgFwUOt83A/jbGMyzmjRoxBuvKcs9tBuorZyUwIsZN6E+rtQk2YqMPj4RkDsZ7LRmj6on8sN2rHQIDAQAB
|
|
-----END PUBLIC KEY-----`
|
|
|
|
func TestBuildOpURLStructure(t *testing.T) {
|
|
pub, err := auth.ParsePublicKey(testPublicKeyPEM)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
now := time.Date(2026, 8, 5, 2, 30, 0, 0, time.UTC)
|
|
s := Signer{
|
|
PublicKey: pub,
|
|
Now: func() time.Time { return now },
|
|
Nonce: func() string { return "fixed-nonce-1234567890" },
|
|
}
|
|
creds := auth.Credentials{AccessKey: "AK123", SecretKey: "SK456", Token: "TOK789"}
|
|
u, err := s.BuildOpURL("/api/acepilot/zhanlu/v1/login", creds, "https://ecloud.10086.cn", "POST")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.HasPrefix(u, "https://ecloud.10086.cn/api/acepilot/zhanlu/v1/login?") {
|
|
t.Fatalf("url = %s", u)
|
|
}
|
|
parsed, err := url.Parse(u)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
q := parsed.Query()
|
|
if q.Get("AccessKey") != "AK123" {
|
|
t.Errorf("AccessKey = %q", q.Get("AccessKey"))
|
|
}
|
|
if q.Get("SignatureMethod") != "HmacSHA1" {
|
|
t.Errorf("SignatureMethod = %q", q.Get("SignatureMethod"))
|
|
}
|
|
if q.Get("SignatureVersion") != "V2.0" {
|
|
t.Errorf("SignatureVersion = %q", q.Get("SignatureVersion"))
|
|
}
|
|
if q.Get("Version") != "2016-12-05" {
|
|
t.Errorf("Version = %q", q.Get("Version"))
|
|
}
|
|
// Beijing time (UTC+8) formatted with Z suffix, matching the plugin's yE9.
|
|
if q.Get("Timestamp") != "2026-08-05T10:30:00Z" {
|
|
t.Errorf("Timestamp = %q, want 2026-08-05T10:30:00Z (Beijing time)", q.Get("Timestamp"))
|
|
}
|
|
if q.Get("Signature") == "" {
|
|
t.Error("Signature is empty")
|
|
}
|
|
if _, err := hex.DecodeString(q.Get("Signature")); err != nil {
|
|
t.Errorf("Signature is not hex: %v", err)
|
|
}
|
|
authz := q.Get("authorization")
|
|
if authz == "" {
|
|
t.Error("authorization is empty")
|
|
}
|
|
}
|
|
|
|
func TestBuildOpURLMissingCreds(t *testing.T) {
|
|
s := Signer{}
|
|
if _, err := s.BuildOpURL("/x", auth.Credentials{}, "http://x", "POST"); err == nil {
|
|
t.Fatal("expected error for missing credentials")
|
|
}
|
|
}
|