Some checks failed
Build & Sign Wraith / Build Windows + Sign (push) Has been cancelled
- OAuth PKCE flow for Max subscription auth (no API key needed) - Claude API client with SSE streaming (Messages API v1) - 16 tool definitions: terminal, SFTP, RDP, session management - Tool dispatch router mapping to existing Wraith services - Conversation manager with SQLite persistence - Terminal output ring buffer for AI context - RDP screenshot encoder (RGBA → JPEG with downscaling) - Wired into Wails app as AIService Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
92 lines
2.3 KiB
Go
92 lines
2.3 KiB
Go
package ai
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/vstockwell/wraith/internal/db"
|
|
"github.com/vstockwell/wraith/internal/settings"
|
|
"github.com/vstockwell/wraith/internal/vault"
|
|
)
|
|
|
|
func TestGenerateCodeVerifier(t *testing.T) {
|
|
v, err := generateCodeVerifier()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
// 32 bytes -> 43 base64url chars (no padding)
|
|
if len(v) != 43 {
|
|
t.Errorf("expected verifier length 43, got %d", len(v))
|
|
}
|
|
|
|
if !isBase64URL(v) {
|
|
t.Errorf("verifier contains non-base64url characters: %s", v)
|
|
}
|
|
|
|
// Should be different each time
|
|
v2, _ := generateCodeVerifier()
|
|
if v == v2 {
|
|
t.Error("two generated verifiers should not be identical")
|
|
}
|
|
}
|
|
|
|
func TestGenerateCodeChallenge(t *testing.T) {
|
|
verifier := "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
|
|
|
|
challenge := generateCodeChallenge(verifier)
|
|
|
|
// Verify it matches a manually computed S256 hash
|
|
h := sha256.Sum256([]byte(verifier))
|
|
expected := base64.RawURLEncoding.EncodeToString(h[:])
|
|
|
|
if challenge != expected {
|
|
t.Errorf("expected challenge %s, got %s", expected, challenge)
|
|
}
|
|
|
|
// Same verifier should produce the same challenge (deterministic)
|
|
challenge2 := generateCodeChallenge(verifier)
|
|
if challenge != challenge2 {
|
|
t.Error("challenge should be deterministic for the same verifier")
|
|
}
|
|
}
|
|
|
|
func TestGenerateState(t *testing.T) {
|
|
s, err := generateState()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
// 32 bytes -> 43 base64url chars (no padding)
|
|
if len(s) != 43 {
|
|
t.Errorf("expected state length 43, got %d", len(s))
|
|
}
|
|
|
|
if !isBase64URL(s) {
|
|
t.Errorf("state contains non-base64url characters: %s", s)
|
|
}
|
|
}
|
|
|
|
func TestIsAuthenticatedWhenNoTokens(t *testing.T) {
|
|
database, err := db.Open(filepath.Join(t.TempDir(), "test.db"))
|
|
if err != nil {
|
|
t.Fatalf("open db: %v", err)
|
|
}
|
|
if err := db.Migrate(database); err != nil {
|
|
t.Fatalf("migrate: %v", err)
|
|
}
|
|
t.Cleanup(func() { database.Close() })
|
|
|
|
settingsSvc := settings.NewSettingsService(database)
|
|
key := vault.DeriveKey("test-password", []byte("test-salt-1234567890123456789012"))
|
|
vaultSvc := vault.NewVaultService(key)
|
|
|
|
oauth := NewOAuthManager(settingsSvc, vaultSvc)
|
|
|
|
if oauth.IsAuthenticated() {
|
|
t.Error("expected IsAuthenticated to return false when no tokens are stored")
|
|
}
|
|
}
|