173 lines
4.8 KiB
Go
173 lines
4.8 KiB
Go
package matrix
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// fakeCryptoHelper implements cryptoHelper for testing.
|
|
type fakeCryptoHelper struct {
|
|
initErr error
|
|
closed bool
|
|
accountID string
|
|
}
|
|
|
|
func (f *fakeCryptoHelper) Init(ctx context.Context) error { return f.initErr }
|
|
func (f *fakeCryptoHelper) Close() error { f.closed = true; return nil }
|
|
func (f *fakeCryptoHelper) SetAccountID(id string) { f.accountID = id }
|
|
|
|
// fakeCryptoIniter implements cryptoIniter for testing.
|
|
type fakeCryptoIniter struct {
|
|
calls int
|
|
helpers []*fakeCryptoHelper // one per call
|
|
}
|
|
|
|
func (f *fakeCryptoIniter) newHelper(pickleKey []byte, storePath string) (cryptoHelper, error) {
|
|
idx := f.calls
|
|
f.calls++
|
|
if idx < len(f.helpers) {
|
|
return f.helpers[idx], nil
|
|
}
|
|
return &fakeCryptoHelper{}, nil
|
|
}
|
|
|
|
func TestInitCryptoCore_Success(t *testing.T) {
|
|
dir := t.TempDir()
|
|
storePath := filepath.Join(dir, "crypto", "crypto.db")
|
|
|
|
initer := &fakeCryptoIniter{
|
|
helpers: []*fakeCryptoHelper{{initErr: nil}},
|
|
}
|
|
|
|
closer, _, err := initCryptoCore(context.Background(), storePath, "", "fake-token", "test-agent", initer, slog.Default())
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if closer == nil {
|
|
t.Error("expected non-nil closer")
|
|
}
|
|
if initer.calls != 1 {
|
|
t.Errorf("expected 1 call to newHelper, got %d", initer.calls)
|
|
}
|
|
}
|
|
|
|
func TestInitCryptoCore_AutoRecoveryOnStaleStore(t *testing.T) {
|
|
dir := t.TempDir()
|
|
storePath := filepath.Join(dir, "crypto", "crypto.db")
|
|
|
|
// Create a stale crypto.db file.
|
|
_ = os.MkdirAll(filepath.Dir(storePath), 0700)
|
|
_ = os.WriteFile(storePath, []byte("stale"), 0o644)
|
|
|
|
// First call fails with "not marked as shared", second succeeds.
|
|
initer := &fakeCryptoIniter{
|
|
helpers: []*fakeCryptoHelper{
|
|
{initErr: errors.New("device keys not marked as shared")},
|
|
{initErr: nil},
|
|
},
|
|
}
|
|
|
|
_, _, err := initCryptoCore(context.Background(), storePath, "", "fake-token", "test-agent", initer, slog.Default())
|
|
if err != nil {
|
|
t.Fatalf("expected auto-recovery to succeed, got: %v", err)
|
|
}
|
|
if initer.calls != 2 {
|
|
t.Errorf("expected 2 calls (fail + retry), got %d", initer.calls)
|
|
}
|
|
}
|
|
|
|
func TestInitCryptoCore_AutoRecoveryFailsTwice(t *testing.T) {
|
|
dir := t.TempDir()
|
|
storePath := filepath.Join(dir, "crypto", "crypto.db")
|
|
|
|
_ = os.MkdirAll(filepath.Dir(storePath), 0700)
|
|
_ = os.WriteFile(storePath, []byte("stale"), 0o644)
|
|
|
|
initer := &fakeCryptoIniter{
|
|
helpers: []*fakeCryptoHelper{
|
|
{initErr: errors.New("not marked as shared")},
|
|
{initErr: errors.New("still broken after recovery")},
|
|
},
|
|
}
|
|
|
|
_, _, err := initCryptoCore(context.Background(), storePath, "", "fake-token", "test-agent", initer, slog.Default())
|
|
if err == nil {
|
|
t.Fatal("expected error when recovery also fails")
|
|
}
|
|
if !strings.Contains(err.Error(), "after auto-recovery") {
|
|
t.Errorf("expected 'after auto-recovery' in error, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestInitCryptoCore_NonRecoverableError(t *testing.T) {
|
|
dir := t.TempDir()
|
|
storePath := filepath.Join(dir, "crypto", "crypto.db")
|
|
|
|
initer := &fakeCryptoIniter{
|
|
helpers: []*fakeCryptoHelper{
|
|
{initErr: errors.New("connection refused")},
|
|
},
|
|
}
|
|
|
|
_, _, err := initCryptoCore(context.Background(), storePath, "", "fake-token", "test-agent", initer, slog.Default())
|
|
if err == nil {
|
|
t.Fatal("expected error for non-recoverable failure")
|
|
}
|
|
if !strings.Contains(err.Error(), "init e2ee") {
|
|
t.Errorf("expected 'init e2ee' in error, got: %v", err)
|
|
}
|
|
// Should NOT have retried.
|
|
if initer.calls != 1 {
|
|
t.Errorf("expected 1 call (no retry for non-stale error), got %d", initer.calls)
|
|
}
|
|
}
|
|
|
|
func TestResolvePickleKey_BadHex(t *testing.T) {
|
|
_, err := resolvePickleKey("not-hex!", "token")
|
|
if err == nil {
|
|
t.Fatal("expected error for invalid hex pickle key")
|
|
}
|
|
if !strings.Contains(err.Error(), "decode pickle_key_env") {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestResolvePickleKey_DeriveFromToken(t *testing.T) {
|
|
key, err := resolvePickleKey("", "my-access-token")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(key) != 32 {
|
|
t.Errorf("expected 32-byte sha256 key, got %d bytes", len(key))
|
|
}
|
|
}
|
|
|
|
func TestResolvePickleKey_Explicit(t *testing.T) {
|
|
hexKey := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
key, err := resolvePickleKey(hexKey, "ignored")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(key) != 32 {
|
|
t.Errorf("expected 32 bytes, got %d", len(key))
|
|
}
|
|
}
|
|
|
|
func TestInitHelper_SetsAccountID(t *testing.T) {
|
|
helper := &fakeCryptoHelper{}
|
|
initer := &fakeCryptoIniter{helpers: []*fakeCryptoHelper{helper}}
|
|
|
|
_, err := initHelper(context.Background(), initer, []byte("key"), "/fake", "my-agent")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if helper.accountID != "my-agent" {
|
|
t.Errorf("expected accountID='my-agent', got '%s'", helper.accountID)
|
|
}
|
|
}
|