mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-09-02 12:20:22 +00:00
Push two deferred packages above their coverage floors: - internal/providers 57.0% -> 62.2% (+5.2%, target ≥60%) Targeted pure-logic adapter transformation code: registry, openai/dashscope/codex ToRequest/FromResponse/FromStreamChunk, Azure header branch, token source propagation, function_call + incomplete + reasoning parse branches. - internal/channels/zalo 7.2% -> 65.3% (+58.1%, target ≥20%) Factory table-driven creds/config cases, New() defaults, callAPI success/error/malformed, getMe/getUpdates, sendMessage/sendPhoto, Send routing + photo extraction, Stop signal, processUpdate dispatch, handleTextMessage empty-sender drop, downloadMedia four branches (png/jpg fallback/404/empty). Minimal refactor: converted package-level apiBase const to var in internal/channels/zalo/zalo.go so tests can point at httptest server. Zero runtime behavior change; tests restore the original value via t.Cleanup. Ratchet bumps (scripts/coverage_thresholds.json): - internal/providers: 57.00 -> 62.15 - internal/channels/zalo: 7.20 -> 65.25 Race clean, dual build (default + -tags sqliteonly) clean, personal/ sub-packages untouched.
123 lines
3.8 KiB
Go
123 lines
3.8 KiB
Go
package providers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestDashScopeAdapter_Basics verifies:
|
|
// - Name() returns "dashscope" (NOT openai — wire format differs conceptually)
|
|
// - StreamWithTools is overridden to false (DashScope falls back to non-stream for tool calls)
|
|
// - Other capabilities inherit from OpenAI
|
|
func TestDashScopeAdapter_Basics(t *testing.T) {
|
|
a, err := NewDashScopeAdapter(ProviderConfig{APIKey: "sk-ds"})
|
|
if err != nil {
|
|
t.Fatalf("NewDashScopeAdapter error: %v", err)
|
|
}
|
|
if a.Name() != "dashscope" {
|
|
t.Errorf("Name() = %q, want dashscope", a.Name())
|
|
}
|
|
caps := a.Capabilities()
|
|
if caps.StreamWithTools {
|
|
t.Error("DashScope must NOT support StreamWithTools (non-stream fallback on tool calls)")
|
|
}
|
|
if !caps.Streaming {
|
|
t.Error("expected Streaming=true (inherited)")
|
|
}
|
|
if !caps.ToolCalling {
|
|
t.Error("expected ToolCalling=true (inherited)")
|
|
}
|
|
}
|
|
|
|
// TestDashScopeAdapter_DefaultsApplied verifies BaseURL and Model defaults
|
|
// are injected when the caller leaves them empty.
|
|
func TestDashScopeAdapter_DefaultsApplied(t *testing.T) {
|
|
a, err := NewDashScopeAdapter(ProviderConfig{APIKey: "sk-ds"})
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
body, _, err := a.ToRequest(ChatRequest{
|
|
Messages: []Message{{Role: "user", Content: "ping"}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("ToRequest: %v", err)
|
|
}
|
|
var m map[string]any
|
|
_ = json.Unmarshal(body, &m)
|
|
model, _ := m["model"].(string)
|
|
if model == "" {
|
|
t.Fatal("model missing from request body")
|
|
}
|
|
// Default model constant is dashscopeDefaultModel — confirm it flowed through.
|
|
if model != dashscopeDefaultModel {
|
|
t.Errorf("model = %q, want %q (default)", model, dashscopeDefaultModel)
|
|
}
|
|
}
|
|
|
|
// TestDashScopeAdapter_ToRequestDelegates verifies the adapter produces the
|
|
// same wire format as OpenAIAdapter for equivalent input (since DashScope is
|
|
// an OpenAI-compatible endpoint).
|
|
func TestDashScopeAdapter_ToRequestDelegates(t *testing.T) {
|
|
a, _ := NewDashScopeAdapter(ProviderConfig{APIKey: "sk", Model: "qwen-plus"})
|
|
body, headers, err := a.ToRequest(ChatRequest{
|
|
Messages: []Message{{Role: "user", Content: "hello qwen"}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("ToRequest: %v", err)
|
|
}
|
|
if !strings.HasPrefix(headers.Get("Authorization"), "Bearer sk") {
|
|
t.Errorf("Authorization = %q, want Bearer sk...", headers.Get("Authorization"))
|
|
}
|
|
var m map[string]any
|
|
_ = json.Unmarshal(body, &m)
|
|
if m["model"] != "qwen-plus" {
|
|
t.Errorf("model = %v, want qwen-plus", m["model"])
|
|
}
|
|
}
|
|
|
|
// TestDashScopeAdapter_FromResponseDelegates verifies response parsing
|
|
// works against the OpenAI-compat shape.
|
|
func TestDashScopeAdapter_FromResponseDelegates(t *testing.T) {
|
|
a, _ := NewDashScopeAdapter(ProviderConfig{APIKey: "sk"})
|
|
raw := []byte(`{
|
|
"choices":[{"index":0,"message":{"role":"assistant","content":"hi"},"finish_reason":"stop"}],
|
|
"usage":{"prompt_tokens":3,"completion_tokens":1,"total_tokens":4}
|
|
}`)
|
|
resp, err := a.FromResponse(raw)
|
|
if err != nil {
|
|
t.Fatalf("FromResponse: %v", err)
|
|
}
|
|
if resp.Content != "hi" {
|
|
t.Errorf("Content = %q, want hi", resp.Content)
|
|
}
|
|
if resp.Usage == nil || resp.Usage.TotalTokens != 4 {
|
|
t.Errorf("Usage TotalTokens = %v, want 4", resp.Usage)
|
|
}
|
|
}
|
|
|
|
// TestDashScopeAdapter_FromStreamChunkDelegates verifies stream chunk parsing
|
|
// reuses OpenAI logic end-to-end.
|
|
func TestDashScopeAdapter_FromStreamChunkDelegates(t *testing.T) {
|
|
a, _ := NewDashScopeAdapter(ProviderConfig{APIKey: "sk"})
|
|
|
|
sc, err := a.FromStreamChunk([]byte("[DONE]"))
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if sc == nil || !sc.Done {
|
|
t.Errorf("want Done, got %+v", sc)
|
|
}
|
|
|
|
sc2, err := a.FromStreamChunk([]byte(`{"choices":[{"delta":{"content":"x"}}]}`))
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
if sc2 == nil || sc2.Content != "x" {
|
|
t.Errorf("want Content=x, got %+v", sc2)
|
|
}
|
|
}
|
|
|
|
// ensure adapter satisfies the interface at compile time.
|
|
var _ ProviderAdapter = (*DashScopeAdapter)(nil)
|