Dateien nach "/" hochladen
This commit is contained in:
+126
@@ -0,0 +1,126 @@
|
||||
package storage_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mailarchive/internal/storage"
|
||||
)
|
||||
|
||||
func TestSaveAndLoad(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
store, err := storage.New(dir)
|
||||
if err != nil {
|
||||
t.Fatalf("New: %v", err)
|
||||
}
|
||||
|
||||
raw := []byte("From: alice@example.com\r\nSubject: Test\r\n\r\nHello World")
|
||||
id, err := store.Save(raw, time.Now())
|
||||
if err != nil {
|
||||
t.Fatalf("Save: %v", err)
|
||||
}
|
||||
if len(id) != 64 {
|
||||
t.Errorf("expected 64-char SHA256 hex, got %d chars", len(id))
|
||||
}
|
||||
|
||||
got, err := store.Load(id)
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if !bytes.Equal(raw, got) {
|
||||
t.Errorf("loaded content mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeduplication(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
store, err := storage.New(dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
raw := []byte("From: alice@example.com\r\n\r\nDuplicate test")
|
||||
id1, err := store.Save(raw, time.Now())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
id2, err := store.Save(raw, time.Now())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if id1 != id2 {
|
||||
t.Errorf("duplicate mail produced different IDs: %s vs %s", id1, id2)
|
||||
}
|
||||
|
||||
// Only one file should exist
|
||||
count := 0
|
||||
filepath.Walk(filepath.Join(dir, "store"), func(p string, info os.FileInfo, _ error) error {
|
||||
if !info.IsDir() { count++ }
|
||||
return nil
|
||||
})
|
||||
if count != 1 {
|
||||
t.Errorf("expected 1 stored file after dedup, got %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
store, err := storage.New(dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
raw := []byte("From: alice@example.com\r\n\r\nDelete me")
|
||||
id, _ := store.Save(raw, time.Now())
|
||||
|
||||
if err := store.Delete(id); err != nil {
|
||||
t.Fatalf("Delete: %v", err)
|
||||
}
|
||||
if _, err := store.Load(id); err == nil {
|
||||
t.Error("Load after Delete should return error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStats(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
store, err := storage.New(dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mails := [][]byte{
|
||||
[]byte("From: a@x.com\r\n\r\nMail 1"),
|
||||
[]byte("From: b@x.com\r\n\r\nMail 2"),
|
||||
[]byte("From: c@x.com\r\n\r\nMail 3"),
|
||||
}
|
||||
for _, m := range mails {
|
||||
store.Save(m, time.Now())
|
||||
}
|
||||
|
||||
stats, err := store.Stats()
|
||||
if err != nil {
|
||||
t.Fatalf("Stats: %v", err)
|
||||
}
|
||||
if stats.TotalMails != 3 {
|
||||
t.Errorf("expected 3 mails, got %d", stats.TotalMails)
|
||||
}
|
||||
if stats.TotalBytes <= 0 {
|
||||
t.Error("expected positive TotalBytes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStorageDirectoryCreation(t *testing.T) {
|
||||
dir := filepath.Join(t.TempDir(), "nested", "path")
|
||||
_, err := storage.New(dir)
|
||||
if err != nil {
|
||||
t.Fatalf("New with nested path: %v", err)
|
||||
}
|
||||
for _, sub := range []string{"store", "attachments", "meta"} {
|
||||
if _, err := os.Stat(filepath.Join(dir, sub)); os.IsNotExist(err) {
|
||||
t.Errorf("expected subdirectory %s to be created", sub)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
package userstore_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mailarchive/internal/userstore"
|
||||
)
|
||||
|
||||
func newTestStore(t *testing.T) *userstore.Store {
|
||||
t.Helper()
|
||||
s, err := userstore.New(filepath.Join(t.TempDir(), "users.db"))
|
||||
if err != nil {
|
||||
t.Fatalf("userstore.New: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { s.Close() })
|
||||
return s
|
||||
}
|
||||
|
||||
func TestCreateAndGetUser(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
|
||||
u, err := s.Create(userstore.CreateUserRequest{
|
||||
Username: "alice",
|
||||
Email: "alice@example.com",
|
||||
Password: "secret123",
|
||||
Role: userstore.RoleAdmin,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Create: %v", err)
|
||||
}
|
||||
if u.ID == 0 {
|
||||
t.Error("expected non-zero ID")
|
||||
}
|
||||
if u.Username != "alice" {
|
||||
t.Errorf("Username = %q", u.Username)
|
||||
}
|
||||
if u.Role != userstore.RoleAdmin {
|
||||
t.Errorf("Role = %q", u.Role)
|
||||
}
|
||||
if u.Source != "local" {
|
||||
t.Errorf("Source = %q, want local", u.Source)
|
||||
}
|
||||
|
||||
got, err := s.GetByID(u.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetByID: %v", err)
|
||||
}
|
||||
if got.Email != "alice@example.com" {
|
||||
t.Errorf("Email = %q", got.Email)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyPassword(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
_, err := s.Create(userstore.CreateUserRequest{
|
||||
Username: "bob", Email: "bob@example.com",
|
||||
Password: "correcthorse", Role: userstore.RoleUser,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Correct password
|
||||
u, err := s.VerifyPassword("bob", "correcthorse")
|
||||
if err != nil {
|
||||
t.Errorf("VerifyPassword correct: %v", err)
|
||||
}
|
||||
if u.Username != "bob" {
|
||||
t.Errorf("Username = %q", u.Username)
|
||||
}
|
||||
|
||||
// Wrong password
|
||||
if _, err := s.VerifyPassword("bob", "wrongpassword"); err == nil {
|
||||
t.Error("expected error for wrong password")
|
||||
}
|
||||
|
||||
// Non-existent user
|
||||
if _, err := s.VerifyPassword("nobody", "x"); err == nil {
|
||||
t.Error("expected error for unknown user")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateUser(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
u, _ := s.Create(userstore.CreateUserRequest{
|
||||
Username: "carol", Email: "carol@old.com",
|
||||
Password: "pw", Role: userstore.RoleUser,
|
||||
})
|
||||
|
||||
newEmail := "carol@new.com"
|
||||
newRole := userstore.RoleAuditor
|
||||
updated, err := s.Update(u.ID, userstore.UpdateUserRequest{
|
||||
Email: &newEmail,
|
||||
Role: &newRole,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Update: %v", err)
|
||||
}
|
||||
if updated.Email != "carol@new.com" {
|
||||
t.Errorf("Email after update = %q", updated.Email)
|
||||
}
|
||||
if updated.Role != userstore.RoleAuditor {
|
||||
t.Errorf("Role after update = %q", updated.Role)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisableUser(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
u, _ := s.Create(userstore.CreateUserRequest{
|
||||
Username: "dave", Email: "dave@x.com",
|
||||
Password: "pw", Role: userstore.RoleUser,
|
||||
})
|
||||
|
||||
active := false
|
||||
s.Update(u.ID, userstore.UpdateUserRequest{Active: &active})
|
||||
|
||||
if _, err := s.VerifyPassword("dave", "pw"); err == nil {
|
||||
t.Error("disabled user should not be able to login")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteUser(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
u, _ := s.Create(userstore.CreateUserRequest{
|
||||
Username: "eve", Email: "eve@x.com",
|
||||
Password: "pw", Role: userstore.RoleUser,
|
||||
})
|
||||
|
||||
if err := s.Delete(u.ID); err != nil {
|
||||
t.Fatalf("Delete: %v", err)
|
||||
}
|
||||
if _, err := s.GetByID(u.ID); err == nil {
|
||||
t.Error("GetByID should error after delete")
|
||||
}
|
||||
// Delete non-existent should error
|
||||
if err := s.Delete(u.ID); err == nil {
|
||||
t.Error("second delete should return error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestListUsers(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
users := []userstore.CreateUserRequest{
|
||||
{Username: "u1", Email: "u1@x.com", Password: "pw", Role: userstore.RoleUser},
|
||||
{Username: "u2", Email: "u2@x.com", Password: "pw", Role: userstore.RoleAdmin},
|
||||
{Username: "u3", Email: "u3@x.com", Password: "pw", Role: userstore.RoleAuditor},
|
||||
{Username: "u4", Email: "u4@x.com", Password: "pw", Role: userstore.RoleUser},
|
||||
}
|
||||
for _, req := range users {
|
||||
s.Create(req)
|
||||
}
|
||||
|
||||
all, err := s.List("")
|
||||
if err != nil {
|
||||
t.Fatalf("List all: %v", err)
|
||||
}
|
||||
if len(all) != 4 {
|
||||
t.Errorf("List all: got %d, want 4", len(all))
|
||||
}
|
||||
|
||||
admins, _ := s.List(userstore.RoleAdmin)
|
||||
if len(admins) != 1 {
|
||||
t.Errorf("List admin: got %d, want 1", len(admins))
|
||||
}
|
||||
|
||||
regular, _ := s.List(userstore.RoleUser)
|
||||
if len(regular) != 2 {
|
||||
t.Errorf("List user: got %d, want 2", len(regular))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenBlacklist(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
|
||||
jti := "test-jti-12345"
|
||||
expires := time.Now().Add(1 * time.Hour)
|
||||
|
||||
if err := s.BlacklistToken(jti, expires); err != nil {
|
||||
t.Fatalf("BlacklistToken: %v", err)
|
||||
}
|
||||
|
||||
blacklisted, err := s.IsBlacklisted(jti)
|
||||
if err != nil {
|
||||
t.Fatalf("IsBlacklisted: %v", err)
|
||||
}
|
||||
if !blacklisted {
|
||||
t.Error("token should be blacklisted")
|
||||
}
|
||||
|
||||
// Non-blacklisted token
|
||||
bl2, _ := s.IsBlacklisted("other-jti")
|
||||
if bl2 {
|
||||
t.Error("unknown token should not be blacklisted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanExpiredTokens(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
|
||||
// Add an already-expired token
|
||||
s.BlacklistToken("expired-jti", time.Now().Add(-1*time.Hour))
|
||||
// Add a valid token
|
||||
s.BlacklistToken("valid-jti", time.Now().Add(1*time.Hour))
|
||||
|
||||
if err := s.CleanExpiredTokens(); err != nil {
|
||||
t.Fatalf("CleanExpiredTokens: %v", err)
|
||||
}
|
||||
|
||||
bl, _ := s.IsBlacklisted("expired-jti")
|
||||
if bl {
|
||||
t.Error("expired token should be cleaned up")
|
||||
}
|
||||
bl2, _ := s.IsBlacklisted("valid-jti")
|
||||
if !bl2 {
|
||||
t.Error("valid token should still be blacklisted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpsertLDAPUser(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
|
||||
u, err := s.UpsertLDAPUser("ldapuser", "ldap@corp.com", userstore.RoleAuditor)
|
||||
if err != nil {
|
||||
t.Fatalf("UpsertLDAPUser: %v", err)
|
||||
}
|
||||
if u.Source != "ldap" {
|
||||
t.Errorf("Source = %q, want ldap", u.Source)
|
||||
}
|
||||
|
||||
// Second upsert should update, not duplicate
|
||||
u2, err := s.UpsertLDAPUser("ldapuser", "ldap@corp.com", userstore.RoleAuditor)
|
||||
if err != nil {
|
||||
t.Fatalf("UpsertLDAPUser second: %v", err)
|
||||
}
|
||||
if u2.ID != u.ID {
|
||||
t.Error("second upsert should not create a new record")
|
||||
}
|
||||
|
||||
all, _ := s.List("")
|
||||
if len(all) != 1 {
|
||||
t.Errorf("expected 1 user after double upsert, got %d", len(all))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user