NAM-APJATEL-BACKEND/usecase/nearest_device_test.go

382 lines
10 KiB
Go

package usecase
import (
"errors"
"testing"
"time"
"users_management/m/model/dto/req"
"users_management/m/model/entity"
"github.com/google/uuid"
)
type mockNearestDeviceRepo struct {
GetNearestDevicesFunc func(longitude, latitude, radius float64, limit int, province, city, district, deviceType *string) ([]entity.DeviceWithDistance, error)
GetDeviceByIDWithConnectionsFunc func(id uuid.UUID) (entity.Device, error)
CountConnectionsByDeviceIDFunc func(deviceID uuid.UUID) (int, int, int, error)
}
func (m *mockNearestDeviceRepo) GetNearestDevices(longitude, latitude, radius float64, limit int, province, city, district, deviceType *string) ([]entity.DeviceWithDistance, error) {
if m.GetNearestDevicesFunc != nil {
return m.GetNearestDevicesFunc(longitude, latitude, radius, limit, province, city, district, deviceType)
}
return nil, nil
}
func (m *mockNearestDeviceRepo) GetDeviceByIDWithConnections(id uuid.UUID) (entity.Device, error) {
if m.GetDeviceByIDWithConnectionsFunc != nil {
return m.GetDeviceByIDWithConnectionsFunc(id)
}
return entity.Device{}, nil
}
func (m *mockNearestDeviceRepo) GetBackbonesByDeviceID(deviceID uuid.UUID) ([]entity.Backbone, error) {
return nil, nil
}
func (m *mockNearestDeviceRepo) GetFishbonesByDeviceID(deviceID uuid.UUID) ([]entity.Fishbone, error) {
return nil, nil
}
func (m *mockNearestDeviceRepo) GetTowersByDeviceID(deviceID uuid.UUID) ([]entity.Tower, error) {
return nil, nil
}
func (m *mockNearestDeviceRepo) CountConnectionsByDeviceID(deviceID uuid.UUID) (backboneCount, fishboneCount, towerCount int, err error) {
if m.CountConnectionsByDeviceIDFunc != nil {
return m.CountConnectionsByDeviceIDFunc(deviceID)
}
return 0, 0, 0, nil
}
func (m *mockNearestDeviceRepo) GetNearestTowers(longitude, latitude, radius float64, limit int, province, city, district *string) ([]entity.TowerWithDistance, error) {
return nil, nil
}
func (m *mockNearestDeviceRepo) GetTowerByIDWithConnections(id uuid.UUID) (entity.Tower, error) {
return entity.Tower{}, nil
}
type mockGeocoder struct{}
func (m *mockGeocoder) GetAddressFromCoordinates(lat, lng float64) (string, error) {
return "Mock Address, City", nil
}
func TestGetNearestDevices_Success(t *testing.T) {
deviceID := uuid.New()
provinceStr := "Jakarta"
cityStr := "Jakarta Selatan"
mockRepo := &mockNearestDeviceRepo{
GetNearestDevicesFunc: func(longitude, latitude, radius float64, limit int, prov, cty, district, deviceType *string) ([]entity.DeviceWithDistance, error) {
if longitude != 106.8 {
t.Errorf("expected longitude 106.8, got %f", longitude)
}
if latitude != -6.2 {
t.Errorf("expected latitude -6.2, got %f", latitude)
}
if radius != 5.0 {
t.Errorf("expected radius 5.0, got %f", radius)
}
if limit != 10 {
t.Errorf("expected limit 10, got %d", limit)
}
_ = prov
_ = cty
return []entity.DeviceWithDistance{
{
ID: deviceID,
DeviceCode: "ODP-001",
DeviceType: entity.ODP,
Longitude: 106.81,
Latitude: -6.21,
PortAmount: 8,
Status: entity.ActiveDev,
Province: &provinceStr,
City: &cityStr,
Distance: 1.5,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
}, nil
},
CountConnectionsByDeviceIDFunc: func(deviceID uuid.UUID) (int, int, int, error) {
return 2, 3, 1, nil
},
}
uc := NewNearestDeviceUseCase(mockRepo, &mockGeocoder{})
request := req.NearestDeviceDTO{
Longitude: 106.8,
Latitude: -6.2,
Radius: 0,
Limit: 0,
}
devices, err := uc.GetNearestDevices(request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if len(devices) != 1 {
t.Fatalf("expected 1 device, got %d", len(devices))
}
if devices[0].DeviceCode != "ODP-001" {
t.Errorf("expected device code ODP-001, got %s", devices[0].DeviceCode)
}
}
func TestGetNearestDevices_WithDeviceTypeFilter(t *testing.T) {
deviceType := "ODP"
mockRepo := &mockNearestDeviceRepo{
GetNearestDevicesFunc: func(longitude, latitude, radius float64, limit int, province, city, district, deviceType *string) ([]entity.DeviceWithDistance, error) {
if deviceType == nil || *deviceType != "ODP" {
t.Errorf("expected deviceType filter to be ODP")
}
return []entity.DeviceWithDistance{
{
ID: uuid.New(),
DeviceCode: "ODP-002",
DeviceType: entity.ODP,
Longitude: 106.82,
Latitude: -6.22,
PortAmount: 16,
Status: entity.ActiveDev,
Distance: 2.5,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
}, nil
},
CountConnectionsByDeviceIDFunc: func(deviceID uuid.UUID) (int, int, int, error) {
return 1, 2, 0, nil
},
}
uc := NewNearestDeviceUseCase(mockRepo, &mockGeocoder{})
request := req.NearestDeviceDTO{
Longitude: 106.8,
Latitude: -6.2,
Radius: 5.0,
Limit: 10,
DeviceType: &deviceType,
}
devices, err := uc.GetNearestDevices(request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if len(devices) != 1 {
t.Fatalf("expected 1 device, got %d", len(devices))
}
if devices[0].DeviceType != "ODP" {
t.Errorf("expected device type ODP, got %s", devices[0].DeviceType)
}
}
func TestGetNearestDevices_RepositoryError(t *testing.T) {
mockRepo := &mockNearestDeviceRepo{
GetNearestDevicesFunc: func(longitude, latitude, radius float64, limit int, province, city, district, deviceType *string) ([]entity.DeviceWithDistance, error) {
return nil, errors.New("database connection failed")
},
}
uc := NewNearestDeviceUseCase(mockRepo, &mockGeocoder{})
request := req.NearestDeviceDTO{
Longitude: 106.8,
Latitude: -6.2,
}
_, err := uc.GetNearestDevices(request)
if err == nil {
t.Fatal("expected error, got nil")
}
}
func TestGetNearestDevices_EmptyResult(t *testing.T) {
mockRepo := &mockNearestDeviceRepo{
GetNearestDevicesFunc: func(longitude, latitude, radius float64, limit int, province, city, district, deviceType *string) ([]entity.DeviceWithDistance, error) {
return []entity.DeviceWithDistance{}, nil
},
}
uc := NewNearestDeviceUseCase(mockRepo, &mockGeocoder{})
request := req.NearestDeviceDTO{
Longitude: 106.8,
Latitude: -6.2,
}
devices, err := uc.GetNearestDevices(request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if len(devices) != 0 {
t.Errorf("expected 0 devices, got %d", len(devices))
}
}
func TestGetNearestDevices_ValidationError(t *testing.T) {
mockRepo := &mockNearestDeviceRepo{}
uc := NewNearestDeviceUseCase(mockRepo, &mockGeocoder{})
request := req.NearestDeviceDTO{
Longitude: 0,
Latitude: 0,
}
_, err := uc.GetNearestDevices(request)
if err == nil {
t.Fatal("expected validation error for missing required fields")
}
}
func TestGetNearestDevices_CustomRadiusAndLimit(t *testing.T) {
customRadius := 0.3
customLimit := 5
mockRepo := &mockNearestDeviceRepo{
GetNearestDevicesFunc: func(longitude, latitude, radius float64, limit int, province, city, district, deviceType *string) ([]entity.DeviceWithDistance, error) {
if radius != customRadius {
t.Errorf("expected radius %f, got %f", customRadius, radius)
}
if limit != customLimit {
t.Errorf("expected limit %d, got %d", customLimit, limit)
}
return []entity.DeviceWithDistance{}, nil
},
}
uc := NewNearestDeviceUseCase(mockRepo, &mockGeocoder{})
request := req.NearestDeviceDTO{
Longitude: 106.8,
Latitude: -6.2,
Radius: customRadius,
Limit: customLimit,
}
_, err := uc.GetNearestDevices(request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
}
func TestCalculateDistance(t *testing.T) {
tests := []struct {
name string
lat1 float64
lng1 float64
lat2 float64
lng2 float64
expected float64
delta float64
}{
{
name: "Same point",
lat1: -6.2,
lng1: 106.8,
lat2: -6.2,
lng2: 106.8,
expected: 0,
delta: 0.001,
},
{
name: "Jakarta to Bandung",
lat1: -6.2088,
lng1: 106.8456,
lat2: -6.9175,
lng2: 107.6191,
expected: 115.0,
delta: 5.0,
},
{
name: "Short distance ~350m",
lat1: -6.2,
lng1: 106.8,
lat2: -6.20225,
lng2: 106.80225,
expected: 0.35,
delta: 0.05,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := calculateDistance(tt.lat1, tt.lng1, tt.lat2, tt.lng2)
if result < tt.expected-tt.delta || result > tt.expected+tt.delta {
t.Errorf("calculateDistance(%f, %f, %f, %f) = %f, expected %f ± %f",
tt.lat1, tt.lng1, tt.lat2, tt.lng2, result, tt.expected, tt.delta)
}
})
}
}
func TestGetNearestDeviceByID_Success(t *testing.T) {
deviceID := uuid.New()
province := "Jakarta"
city := "Jakarta"
mockRepo := &mockNearestDeviceRepo{
GetDeviceByIDWithConnectionsFunc: func(id uuid.UUID) (entity.Device, error) {
if id != deviceID {
t.Errorf("expected device ID %s, got %s", deviceID, id)
}
return entity.Device{
ID: deviceID,
DeviceCode: "ODP-003",
DeviceType: entity.ODP,
Longitude: 106.81,
Latitude: -6.21,
PortAmount: 8,
Status: entity.ActiveDev,
Province: &province,
City: &city,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}, nil
},
}
uc := NewNearestDeviceUseCase(mockRepo, &mockGeocoder{})
device, err := uc.GetNearestDeviceByID(deviceID, -6.2, 106.8)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if device.DeviceCode != "ODP-003" {
t.Errorf("expected device code ODP-003, got %s", device.DeviceCode)
}
if device.Distance <= 0 {
t.Errorf("expected positive distance, got %f", device.Distance)
}
}
func TestGetNearestDeviceByID_NotFound(t *testing.T) {
deviceID := uuid.New()
mockRepo := &mockNearestDeviceRepo{
GetDeviceByIDWithConnectionsFunc: func(id uuid.UUID) (entity.Device, error) {
return entity.Device{}, errors.New("record not found")
},
}
uc := NewNearestDeviceUseCase(mockRepo, &mockGeocoder{})
_, err := uc.GetNearestDeviceByID(deviceID, -6.2, 106.8)
if err == nil {
t.Fatal("expected error for not found device")
}
}