refactor: move V1 code into v1/ subdirectory

- git mv cmd/ internal/ pkg/ test/ go.mod go.sum Makefile build.sh docs/ v1/
- Create v1/README.md with V1 documentation
- Update root README for V1 + V2 structure
- V1 still builds (cd v1 && go build ./cmd/hostkeeper) and 105 tests pass
- Root is now clean for V2 development
This commit is contained in:
swanadiva
2026-07-07 11:56:27 +07:00
parent 8ebdebedc8
commit 847989df75
68 changed files with 58 additions and 116 deletions
+236
View File
@@ -0,0 +1,236 @@
package crypto_test
import (
"bytes"
"crypto/rand"
"strings"
"testing"
"git.tukangketik.id/swanadiva/hostkeeper/pkg/crypto"
)
// 1.1 DeriveKey determinism — same password + salt → same key
func TestDeriveKeyDeterminism(t *testing.T) {
salt := []byte("1234567890123456")
key1 := crypto.DeriveKey("password", salt)
key2 := crypto.DeriveKey("password", salt)
if !bytes.Equal(key1, key2) {
t.Error("DeriveKey should return same key for same password + salt")
}
}
// 1.2 DeriveKey password variation — different password → different key
func TestDeriveKeyPasswordVariation(t *testing.T) {
salt := []byte("1234567890123456")
key1 := crypto.DeriveKey("password1", salt)
key2 := crypto.DeriveKey("password2", salt)
if bytes.Equal(key1, key2) {
t.Error("DeriveKey should return different keys for different passwords")
}
}
// 1.3 DeriveKey salt variation — different salt → different key
func TestDeriveKeySaltVariation(t *testing.T) {
key1 := crypto.DeriveKey("password", []byte("1234567890123456"))
key2 := crypto.DeriveKey("password", []byte("6543210987654321"))
if bytes.Equal(key1, key2) {
t.Error("DeriveKey should return different keys for different salts")
}
}
// 1.4 DeriveKey empty password — no panic, valid key length
func TestDeriveKeyEmptyPassword(t *testing.T) {
salt := []byte("1234567890123456")
key := crypto.DeriveKey("", salt)
if len(key) != crypto.KeyLength {
t.Errorf("DeriveKey with empty password should return %d bytes, got %d", crypto.KeyLength, len(key))
}
}
// 1.5 Encrypt/Decrypt round-trip
func TestEncryptDecryptRoundTrip(t *testing.T) {
plaintext := []byte("hello world")
encoded, err := crypto.Encrypt(plaintext, "mypassword")
if err != nil {
t.Fatalf("Encrypt failed: %v", err)
}
decoded, err := crypto.Decrypt(encoded, "mypassword")
if err != nil {
t.Fatalf("Decrypt failed: %v", err)
}
if !bytes.Equal(plaintext, decoded) {
t.Errorf("Round-trip failed: got %q, want %q", decoded, plaintext)
}
}
// 1.6 Encrypt empty plaintext
func TestEncryptDecryptEmpty(t *testing.T) {
plaintext := []byte("")
encoded, err := crypto.Encrypt(plaintext, "password")
if err != nil {
t.Fatalf("Encrypt failed: %v", err)
}
decoded, err := crypto.Decrypt(encoded, "password")
if err != nil {
t.Fatalf("Decrypt failed: %v", err)
}
if len(decoded) != 0 {
t.Errorf("Expected empty plaintext, got %d bytes", len(decoded))
}
}
// 1.7 Encrypt large data (1MB)
func TestEncryptDecryptLargeData(t *testing.T) {
plaintext := make([]byte, 1024*1024)
if _, err := rand.Read(plaintext); err != nil {
t.Fatalf("Failed to generate random data: %v", err)
}
encoded, err := crypto.Encrypt(plaintext, "password")
if err != nil {
t.Fatalf("Encrypt failed: %v", err)
}
decoded, err := crypto.Decrypt(encoded, "password")
if err != nil {
t.Fatalf("Decrypt failed: %v", err)
}
if !bytes.Equal(plaintext, decoded) {
t.Error("Large data round-trip failed")
}
}
// 1.8 Encrypt unicode
func TestEncryptDecryptUnicode(t *testing.T) {
plaintext := []byte("こんにちは世界 🌍")
encoded, err := crypto.Encrypt(plaintext, "password")
if err != nil {
t.Fatalf("Encrypt failed: %v", err)
}
decoded, err := crypto.Decrypt(encoded, "password")
if err != nil {
t.Fatalf("Decrypt failed: %v", err)
}
if !bytes.Equal(plaintext, decoded) {
t.Errorf("Unicode round-trip failed: got %q, want %q", decoded, plaintext)
}
}
// 1.9 Encrypt with newlines
func TestEncryptDecryptNewlines(t *testing.T) {
plaintext := []byte("line1\nline2\nline3")
encoded, err := crypto.Encrypt(plaintext, "password")
if err != nil {
t.Fatalf("Encrypt failed: %v", err)
}
decoded, err := crypto.Decrypt(encoded, "password")
if err != nil {
t.Fatalf("Decrypt failed: %v", err)
}
if !bytes.Equal(plaintext, decoded) {
t.Errorf("Newlines round-trip failed: got %q, want %q", decoded, plaintext)
}
}
// 1.10 Wrong password → error
func TestDecryptWrongPassword(t *testing.T) {
plaintext := []byte("secret data")
encoded, err := crypto.Encrypt(plaintext, "correct-password")
if err != nil {
t.Fatalf("Encrypt failed: %v", err)
}
_, err = crypto.Decrypt(encoded, "wrong-password")
if err == nil {
t.Error("Decrypt with wrong password should return error")
}
}
// 1.11 Empty password → error
func TestDecryptEmptyPassword(t *testing.T) {
plaintext := []byte("secret data")
encoded, err := crypto.Encrypt(plaintext, "password")
if err != nil {
t.Fatalf("Encrypt failed: %v", err)
}
_, err = crypto.Decrypt(encoded, "")
if err == nil {
t.Error("Decrypt with empty password should return error")
}
}
// 1.12 IsEncrypted valid ciphertext
func TestIsEncryptedValid(t *testing.T) {
encoded, err := crypto.Encrypt([]byte("test"), "password")
if err != nil {
t.Fatalf("Encrypt failed: %v", err)
}
if !crypto.IsEncrypted(encoded) {
t.Error("IsEncrypted should return true for valid ciphertext")
}
}
// 1.13 IsEncrypted plaintext
func TestIsEncryptedPlaintext(t *testing.T) {
if crypto.IsEncrypted("hello world") {
t.Error("IsEncrypted should return false for plaintext")
}
}
// 1.14 IsEncrypted empty
func TestIsEncryptedEmpty(t *testing.T) {
if crypto.IsEncrypted("") {
t.Error("IsEncrypted should return false for empty string")
}
}
// 1.15 HashPassword determinism
func TestHashPasswordDeterminism(t *testing.T) {
hash1 := crypto.HashPassword("mypassword")
hash2 := crypto.HashPassword("mypassword")
if hash1 != hash2 {
t.Error("HashPassword should return same hash for same password")
}
}
// 1.16 HashPassword variation
func TestHashPasswordVariation(t *testing.T) {
hash1 := crypto.HashPassword("password1")
hash2 := crypto.HashPassword("password2")
if hash1 == hash2 {
t.Error("HashPassword should return different hashes for different passwords")
}
}
// 1.17 Encrypt randomness — same input → different ciphertext
func TestEncryptRandomness(t *testing.T) {
plaintext := []byte("same input")
encoded1, _ := crypto.Encrypt(plaintext, "password")
encoded2, _ := crypto.Encrypt(plaintext, "password")
if encoded1 == encoded2 {
t.Error("Encrypt should produce different ciphertext each time (random salt)")
}
}
// Verify constants
func TestConstants(t *testing.T) {
if crypto.KeyLength != 32 {
t.Errorf("KeyLength = %d, want 32", crypto.KeyLength)
}
if crypto.SaltLength != 16 {
t.Errorf("SaltLength = %d, want 16", crypto.SaltLength)
}
if crypto.Iterations != 100000 {
t.Errorf("Iterations = %d, want 100000", crypto.Iterations)
}
}
// Verify error variables
func TestErrorVars(t *testing.T) {
if crypto.ErrInvalidPassword == nil {
t.Error("ErrInvalidPassword should not be nil")
}
if crypto.ErrDecryptionFailed == nil {
t.Error("ErrDecryptionFailed should not be nil")
}
if !strings.Contains(crypto.ErrDecryptionFailed.Error(), "decryption failed") {
t.Error("ErrDecryptionFailed message should contain 'decryption failed'")
}
}