Files
goclaw/internal/providers/adapter_dashscope_test.go
viettranx abc6d62c95 test(providers,zalo): wave A coverage push
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.
2026-04-11 21:29:32 +07:00

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)