Files
OpenFlare/internal/apps/openflare/cloudflare/logics_test.go
T

194 lines
7.2 KiB
Go

// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package cloudflare
import (
"context"
"testing"
db "github.com/Rain-kl/Wavelet/internal/infra/persistence"
"github.com/Rain-kl/Wavelet/internal/model"
"github.com/Rain-kl/Wavelet/internal/repository"
)
func TestMoveMemberAndBatchOperations(t *testing.T) {
ctx, member1ID := setupCloudflareLogicDB(t)
var dispatchedTasks []string
restoreDispatch := SetDispatchTaskForTest(func(ctx context.Context, taskType string, payload []byte, triggeredBy string) (string, error) {
dispatchedTasks = append(dispatchedTasks, triggeredBy)
return "mock-task-id", nil
})
t.Cleanup(restoreDispatch)
fake := &fakeClient{}
restoreClient := SetClientFactoryForTest(func(string) Client { return fake })
t.Cleanup(restoreClient)
member1, err := repository.GetCFPointingMemberByID(ctx, member1ID)
if err != nil {
t.Fatalf("GetCFPointingMemberByID() error = %v", err)
}
sourceGroupID := member1.GroupID
// Create target group
targetGroup := model.CFPointingGroup{
Name: "secondary",
PrimaryNodeID: 1,
ActiveNodeID: 1,
DefaultProxied: true,
Enabled: true,
}
if err := db.DB(ctx).Create(&targetGroup).Error; err != nil {
t.Fatalf("Create(targetGroup) error = %v", err)
}
// 1. Target group equals source group -> errTargetGroupSame
t.Run("MoveMember target equals source", func(t *testing.T) {
_, err := MoveMember(ctx, sourceGroupID, member1.ID, sourceGroupID)
if err == nil || err.Error() != errTargetGroupSame {
t.Fatalf("MoveMember() error = %v, want %s", err, errTargetGroupSame)
}
})
// 2. Target group does not exist -> errTargetGroupInvalid
t.Run("MoveMember target group invalid", func(t *testing.T) {
_, err := MoveMember(ctx, sourceGroupID, member1.ID, 99999)
if err == nil || err.Error() != errTargetGroupInvalid {
t.Fatalf("MoveMember() error = %v, want %s", err, errTargetGroupInvalid)
}
})
// 3. Successfully move member to target group
t.Run("MoveMember success", func(t *testing.T) {
item, err := MoveMember(ctx, sourceGroupID, member1.ID, targetGroup.ID)
if err != nil {
t.Fatalf("MoveMember() error = %v", err)
}
if item == nil || item.GroupID != targetGroup.ID {
t.Fatalf("MoveMember() returned item group ID = %v, want %d", item, targetGroup.ID)
}
updated, err := repository.GetCFPointingMemberByID(ctx, member1.ID)
if err != nil {
t.Fatalf("GetCFPointingMemberByID() error = %v", err)
}
if updated.GroupID != targetGroup.ID {
t.Errorf("member GroupID = %d, want %d", updated.GroupID, targetGroup.ID)
}
if updated.SyncStatus != model.CFMemberSyncPending {
t.Errorf("member SyncStatus = %s, want %s", updated.SyncStatus, model.CFMemberSyncPending)
}
if updated.LastError != "" {
t.Errorf("member LastError = %q, want empty", updated.LastError)
}
})
// 4. Batch move multiple members
t.Run("BatchMoveMembers", func(t *testing.T) {
// Test empty members error
emptyErr := BatchMoveMembers(ctx, sourceGroupID, MemberBatchMoveInput{MemberIDs: nil, TargetGroupID: targetGroup.ID})
if emptyErr == nil || emptyErr.Error() != errNoMembersSelected {
t.Fatalf("BatchMoveMembers() empty error = %v, want %s", emptyErr, errNoMembersSelected)
}
// Test target equals source error
sameErr := BatchMoveMembers(ctx, sourceGroupID, MemberBatchMoveInput{MemberIDs: []uint{1}, TargetGroupID: sourceGroupID})
if sameErr == nil || sameErr.Error() != errTargetGroupSame {
t.Fatalf("BatchMoveMembers() same group error = %v, want %s", sameErr, errTargetGroupSame)
}
// Test invalid target group error
invalidErr := BatchMoveMembers(ctx, sourceGroupID, MemberBatchMoveInput{MemberIDs: []uint{1}, TargetGroupID: 99999})
if invalidErr == nil || invalidErr.Error() != errTargetGroupInvalid {
t.Fatalf("BatchMoveMembers() invalid group error = %v, want %s", invalidErr, errTargetGroupInvalid)
}
// Create 2 additional members in sourceGroup
domain2 := model.ZoneDomain{ZoneID: 1, Domain: "test2.example.com"}
domain3 := model.ZoneDomain{ZoneID: 1, Domain: "test3.example.com"}
if err := db.DB(ctx).Create(&domain2).Error; err != nil {
t.Fatalf("Create(domain2) error = %v", err)
}
if err := db.DB(ctx).Create(&domain3).Error; err != nil {
t.Fatalf("Create(domain3) error = %v", err)
}
member2 := model.CFPointingMember{GroupID: sourceGroupID, ZoneDomainID: domain2.ID, Proxied: false, SyncStatus: model.CFMemberSyncOK}
member3 := model.CFPointingMember{GroupID: sourceGroupID, ZoneDomainID: domain3.ID, Proxied: true, SyncStatus: model.CFMemberSyncOK}
if err := db.DB(ctx).Create(&member2).Error; err != nil {
t.Fatalf("Create(member2) error = %v", err)
}
if err := db.DB(ctx).Create(&member3).Error; err != nil {
t.Fatalf("Create(member3) error = %v", err)
}
// Perform batch move
if err := BatchMoveMembers(ctx, sourceGroupID, MemberBatchMoveInput{
MemberIDs: []uint{member2.ID, member3.ID},
TargetGroupID: targetGroup.ID,
}); err != nil {
t.Fatalf("BatchMoveMembers() error = %v", err)
}
// Verify updated in DB
for _, mid := range []uint{member2.ID, member3.ID} {
m, err := repository.GetCFPointingMemberByID(ctx, mid)
if err != nil {
t.Fatalf("GetCFPointingMemberByID(%d) error = %v", mid, err)
}
if m.GroupID != targetGroup.ID {
t.Errorf("member %d GroupID = %d, want %d", mid, m.GroupID, targetGroup.ID)
}
if m.SyncStatus != model.CFMemberSyncPending {
t.Errorf("member %d SyncStatus = %s, want %s", mid, m.SyncStatus, model.CFMemberSyncPending)
}
if m.LastError != "" {
t.Errorf("member %d LastError = %q, want empty", mid, m.LastError)
}
}
})
// 5. Batch remove members
t.Run("BatchRemoveMembers", func(t *testing.T) {
// Test empty members error
emptyErr := BatchRemoveMembers(ctx, targetGroup.ID, MemberBatchRemoveInput{MemberIDs: nil})
if emptyErr == nil || emptyErr.Error() != errNoMembersSelected {
t.Fatalf("BatchRemoveMembers() empty error = %v, want %s", emptyErr, errNoMembersSelected)
}
// Create members to remove
domain4 := model.ZoneDomain{ZoneID: 1, Domain: "test4.example.com"}
domain5 := model.ZoneDomain{ZoneID: 1, Domain: "test5.example.com"}
if err := db.DB(ctx).Create(&domain4).Error; err != nil {
t.Fatalf("Create(domain4) error = %v", err)
}
if err := db.DB(ctx).Create(&domain5).Error; err != nil {
t.Fatalf("Create(domain5) error = %v", err)
}
member4 := model.CFPointingMember{GroupID: targetGroup.ID, ZoneDomainID: domain4.ID, Proxied: false, SyncStatus: model.CFMemberSyncOK}
member5 := model.CFPointingMember{GroupID: targetGroup.ID, ZoneDomainID: domain5.ID, Proxied: true, SyncStatus: model.CFMemberSyncOK}
if err := db.DB(ctx).Create(&member4).Error; err != nil {
t.Fatalf("Create(member4) error = %v", err)
}
if err := db.DB(ctx).Create(&member5).Error; err != nil {
t.Fatalf("Create(member5) error = %v", err)
}
// Perform batch remove
if err := BatchRemoveMembers(ctx, targetGroup.ID, MemberBatchRemoveInput{
MemberIDs: []uint{member4.ID, member5.ID},
}); err != nil {
t.Fatalf("BatchRemoveMembers() error = %v", err)
}
// Verify deleted from DB
for _, mid := range []uint{member4.ID, member5.ID} {
_, err := repository.GetCFPointingMemberByID(ctx, mid)
if err == nil {
t.Errorf("GetCFPointingMemberByID(%d) should be deleted, but found", mid)
}
}
})
}