Files
dingtalk-workspace-cli/internal/security/coverage_test.go
T
audanye-sudo c8f1a9a912 chore: fix staticcheck warnings and restore missing .env.example
- Replace nil context with context.TODO() in logger calls
- Fix import ordering and struct field alignment
- Restore .env.example required by open-source audit tests
2026-03-29 00:30:27 +08:00

268 lines
7.5 KiB
Go

package security
import (
"net"
"os"
"path/filepath"
"testing"
"time"
)
// ─── crypto.go ─────────────────────────────────────────────────────────
func TestEncryptDecrypt_RoundTrip(t *testing.T) {
t.Parallel()
plain := []byte("sensitive data here")
password := []byte("test-password-123")
enc, err := Encrypt(plain, password)
if err != nil {
t.Fatalf("Encrypt error: %v", err)
}
dec, err := Decrypt(enc, password)
if err != nil {
t.Fatalf("Decrypt error: %v", err)
}
if string(dec) != string(plain) {
t.Fatalf("round-trip mismatch: %q vs %q", dec, plain)
}
}
func TestEncryptDecrypt_EmptyPlaintext(t *testing.T) {
t.Parallel()
enc, err := Encrypt([]byte{}, []byte("pass"))
if err != nil {
t.Fatalf("Encrypt empty error: %v", err)
}
dec, err := Decrypt(enc, []byte("pass"))
if err != nil {
t.Fatalf("Decrypt empty error: %v", err)
}
if len(dec) != 0 {
t.Fatalf("expected empty, got %d bytes", len(dec))
}
}
func TestDecrypt_TooShort(t *testing.T) {
t.Parallel()
_, err := Decrypt([]byte("short"), []byte("pass"))
if err == nil {
t.Fatal("expected error for short ciphertext")
}
}
func TestDecrypt_Corrupted(t *testing.T) {
t.Parallel()
enc, _ := Encrypt([]byte("data"), []byte("pass"))
// Corrupt the last byte (GCM tag)
enc[len(enc)-1] ^= 0xff
_, err := Decrypt(enc, []byte("pass"))
if err == nil {
t.Fatal("expected error for corrupted ciphertext")
}
}
func TestDecrypt_WrongPassword(t *testing.T) {
t.Parallel()
enc, _ := Encrypt([]byte("secret"), []byte("correct"))
_, err := Decrypt(enc, []byte("wrong"))
if err == nil {
t.Fatal("expected error for wrong password")
}
}
func TestDeriveKey_Deterministic(t *testing.T) {
t.Parallel()
salt := []byte("fixed-salt-value-32-bytes-long!!")
k1 := DeriveKey([]byte("pass"), salt)
k2 := DeriveKey([]byte("pass"), salt)
if len(k1) != KeySize {
t.Fatalf("expected %d bytes, got %d", KeySize, len(k1))
}
for i := range k1 {
if k1[i] != k2[i] {
t.Fatal("same inputs should produce same key")
}
}
}
// ─── fingerprint.go ────────────────────────────────────────────────────
func TestIsVirtualMAC(t *testing.T) {
t.Parallel()
tests := []struct {
mac string
want bool
}{
{"02:42:ac:11:00:02", true}, // Docker
{"00:50:56:c0:00:01", true}, // VMware
{"00:0c:29:ab:cd:ef", true}, // VMware
{"08:00:27:12:34:56", true}, // VirtualBox
{"52:54:00:ab:cd:ef", true}, // KVM/QEMU
{"aa:bb:cc:dd:ee:ff", false}, // physical
{"14:98:77:ab:cd:ef", false}, // physical
}
for _, tt := range tests {
if got := isVirtualMAC(tt.mac); got != tt.want {
t.Errorf("isVirtualMAC(%s) = %v, want %v", tt.mac, got, tt.want)
}
}
}
func TestGetMACAddress_ReturnsNonEmpty(t *testing.T) {
t.Parallel()
mac, err := GetMACAddress()
if err != nil {
t.Skipf("no NIC available: %v", err)
}
if mac == "" {
t.Fatal("expected non-empty MAC address")
}
// Should look like a MAC
if len(mac) < 11 {
t.Fatalf("MAC too short: %s", mac)
}
}
func TestSelectMAC_PhysicalPreferred(t *testing.T) {
t.Parallel()
ifaces := []net.Interface{
{Index: 1, Name: "lo", Flags: net.FlagLoopback, HardwareAddr: nil},
{Index: 2, Name: "eth0", Flags: net.FlagUp, HardwareAddr: net.HardwareAddr{0x02, 0x42, 0xac, 0x11, 0x00, 0x02}}, // Docker virtual
{Index: 3, Name: "enp0s3", Flags: net.FlagUp, HardwareAddr: net.HardwareAddr{0x14, 0x98, 0x77, 0xab, 0xcd, 0xef}}, // physical
}
mac, err := selectMAC(ifaces)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if mac != "14:98:77:ab:cd:ef" {
t.Fatalf("expected physical MAC, got %s", mac)
}
}
func TestSelectMAC_VirtualFallback(t *testing.T) {
t.Parallel()
// Docker container scenario: only loopback + Docker veth
ifaces := []net.Interface{
{Index: 1, Name: "lo", Flags: net.FlagLoopback, HardwareAddr: nil},
{Index: 2, Name: "eth0", Flags: net.FlagUp, HardwareAddr: net.HardwareAddr{0x02, 0x42, 0xac, 0x11, 0x00, 0x02}},
}
mac, err := selectMAC(ifaces)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if mac != "02:42:ac:11:00:02" {
t.Fatalf("expected Docker virtual MAC, got %s", mac)
}
}
func TestSelectMAC_MultipleVirtualPicksFirst(t *testing.T) {
t.Parallel()
ifaces := []net.Interface{
{Index: 1, Name: "lo", Flags: net.FlagLoopback},
{Index: 2, Name: "eth0", Flags: net.FlagUp, HardwareAddr: net.HardwareAddr{0x02, 0x42, 0xbb, 0x00, 0x00, 0x01}},
{Index: 3, Name: "eth1", Flags: net.FlagUp, HardwareAddr: net.HardwareAddr{0x02, 0x42, 0xaa, 0x00, 0x00, 0x01}},
}
mac, err := selectMAC(ifaces)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// Lexicographically: 02:42:aa:... < 02:42:bb:...
if mac != "02:42:aa:00:00:01" {
t.Fatalf("expected lexicographically first virtual MAC, got %s", mac)
}
}
func TestSelectMAC_NoInterfaces(t *testing.T) {
t.Parallel()
_, err := selectMAC(nil)
if err == nil {
t.Fatal("expected error for empty interface list")
}
}
func TestSelectMAC_OnlyLoopback(t *testing.T) {
t.Parallel()
ifaces := []net.Interface{
{Index: 1, Name: "lo", Flags: net.FlagLoopback, HardwareAddr: net.HardwareAddr{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
}
_, err := selectMAC(ifaces)
if err == nil {
t.Fatal("expected error when only loopback exists")
}
}
func TestSelectMAC_NoHardwareAddr(t *testing.T) {
t.Parallel()
ifaces := []net.Interface{
{Index: 1, Name: "tun0", Flags: net.FlagUp, HardwareAddr: nil},
}
_, err := selectMAC(ifaces)
if err == nil {
t.Fatal("expected error for interfaces without hardware address")
}
}
// ─── storage.go ────────────────────────────────────────────────────────
func TestDeleteEncryptedData(t *testing.T) {
t.Parallel()
dir1 := t.TempDir()
dir2 := t.TempDir()
// Create .data files
os.WriteFile(filepath.Join(dir1, DataFileName), []byte("enc1"), 0o600)
os.WriteFile(filepath.Join(dir2, DataFileName), []byte("enc2"), 0o600)
err := DeleteEncryptedData(dir1, dir2)
if err != nil {
t.Fatalf("DeleteEncryptedData error: %v", err)
}
if DataFileExistsInAny(dir1, dir2) {
t.Fatal("files should be deleted")
}
}
func TestDeleteEncryptedData_EmptyDirs(t *testing.T) {
t.Parallel()
err := DeleteEncryptedData("", "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func TestDeleteEncryptedData_NonExistent(t *testing.T) {
t.Parallel()
err := DeleteEncryptedData("/nonexistent/dir")
if err != nil {
t.Fatalf("unexpected error for nonexistent: %v", err)
}
}
func TestSecureTokenStorage_ExistsInFallback(t *testing.T) {
t.Parallel()
primary := t.TempDir()
fallback := t.TempDir()
os.WriteFile(filepath.Join(fallback, DataFileName), []byte("data"), 0o600)
s := NewSecureTokenStorage(primary, fallback, "")
if !s.Exists() {
t.Fatal("should find .data in fallback dir")
}
}
func TestSecureTokenStorage_DeleteTokenBothDirs(t *testing.T) {
t.Parallel()
dir1 := t.TempDir()
dir2 := t.TempDir()
s1 := NewSecureTokenStorage(dir1, "", "aa:bb:cc:dd:ee:ff")
s1.SaveToken(&TokenData{AccessToken: "t", ExpiresAt: time.Now().Add(time.Hour)})
os.WriteFile(filepath.Join(dir2, DataFileName), []byte("enc"), 0o600)
s := NewSecureTokenStorage(dir1, dir2, "aa:bb:cc:dd:ee:ff")
if err := s.DeleteToken(); err != nil {
t.Fatalf("error: %v", err)
}
if DataFileExistsInAny(dir1, dir2) {
t.Fatal("both should be deleted")
}
}