mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
398 lines
13 KiB
Go
398 lines
13 KiB
Go
package service_test
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"bugulma/backend/internal/domain"
|
|
"bugulma/backend/internal/repository"
|
|
"bugulma/backend/internal/service"
|
|
"bugulma/backend/internal/testutils"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/suite"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type GeographicalDataMigrationServiceTestSuite struct {
|
|
suite.Suite
|
|
db *gorm.DB
|
|
siteRepo domain.SiteRepository
|
|
geoRepo domain.GeographicalFeatureRepository
|
|
sqliteDB *sql.DB
|
|
tempDir string
|
|
migrationSvc *service.GeographicalDataMigrationService
|
|
}
|
|
|
|
func (suite *GeographicalDataMigrationServiceTestSuite) SetupTest() {
|
|
suite.db = testutils.SetupTestDB(suite.T())
|
|
suite.siteRepo = repository.NewSiteRepository(suite.db)
|
|
suite.geoRepo = repository.NewGeographicalFeatureRepository(suite.db)
|
|
|
|
// Create temporary directory and SQLite database
|
|
suite.tempDir = suite.T().TempDir()
|
|
sqlitePath := filepath.Join(suite.tempDir, "test_data.db")
|
|
|
|
// Create test SQLite database with sample data
|
|
err := suite.createTestSQLiteDB(sqlitePath)
|
|
suite.Require().NoError(err)
|
|
|
|
// Create migration service
|
|
suite.migrationSvc, err = service.NewGeographicalDataMigrationService(
|
|
suite.db, suite.geoRepo, suite.siteRepo, sqlitePath,
|
|
)
|
|
suite.Require().NoError(err)
|
|
|
|
// Create test organization
|
|
org := &domain.Organization{ID: "org-migration-test", Name: "Migration Test Organization"}
|
|
err = repository.NewOrganizationRepository(suite.db).Create(context.Background(), org)
|
|
suite.Require().NoError(err)
|
|
}
|
|
|
|
func (suite *GeographicalDataMigrationServiceTestSuite) TearDownTest() {
|
|
if suite.migrationSvc != nil {
|
|
suite.migrationSvc.Close()
|
|
}
|
|
os.RemoveAll(suite.tempDir)
|
|
}
|
|
|
|
func TestGeographicalDataMigrationService(t *testing.T) {
|
|
suite.Run(t, new(GeographicalDataMigrationServiceTestSuite))
|
|
}
|
|
|
|
func (suite *GeographicalDataMigrationServiceTestSuite) createTestSQLiteDB(dbPath string) error {
|
|
db, err := sql.Open("sqlite3", dbPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer db.Close()
|
|
|
|
// Create test tables
|
|
createTablesSQL := `
|
|
CREATE TABLE osm_features (
|
|
id TEXT PRIMARY KEY,
|
|
osm_type TEXT,
|
|
osm_id TEXT,
|
|
feature_type TEXT,
|
|
geometry TEXT,
|
|
properties TEXT
|
|
);
|
|
|
|
CREATE TABLE osm_buildings (
|
|
id TEXT PRIMARY KEY,
|
|
osm_type TEXT,
|
|
osm_id TEXT,
|
|
building_type TEXT,
|
|
geometry TEXT,
|
|
properties TEXT
|
|
);
|
|
|
|
CREATE TABLE osm_roads (
|
|
id TEXT PRIMARY KEY,
|
|
osm_type TEXT,
|
|
osm_id TEXT,
|
|
road_type TEXT,
|
|
geometry TEXT,
|
|
properties TEXT
|
|
);
|
|
|
|
CREATE TABLE osm_green_spaces (
|
|
id TEXT PRIMARY KEY,
|
|
osm_type TEXT,
|
|
osm_id TEXT,
|
|
green_space_type TEXT,
|
|
geometry TEXT,
|
|
properties TEXT
|
|
);
|
|
`
|
|
|
|
_, err = db.Exec(createTablesSQL)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Insert test data
|
|
testData := []struct {
|
|
table string
|
|
data []map[string]interface{}
|
|
}{
|
|
{
|
|
table: "osm_features",
|
|
data: []map[string]interface{}{
|
|
{
|
|
"id": "building-1", "osm_type": "way", "osm_id": "1001", "feature_type": "building",
|
|
"geometry": `{"type": "Polygon", "coordinates": [[[13.4, 52.5], [13.41, 52.5], [13.41, 52.51], [13.4, 52.51], [13.4, 52.5]]]}`,
|
|
"properties": `{"building": "office", "name": "Test Office Building"}`,
|
|
},
|
|
{
|
|
"id": "road-1", "osm_type": "way", "osm_id": "2001", "feature_type": "road",
|
|
"geometry": `{"type": "LineString", "coordinates": [[13.4, 52.5], [13.42, 52.5]]}`,
|
|
"properties": `{"highway": "primary", "name": "Test Road"}`,
|
|
},
|
|
{
|
|
"id": "park-1", "osm_type": "way", "osm_id": "3001", "feature_type": "green_space",
|
|
"geometry": `{"type": "Polygon", "coordinates": [[[13.4, 52.5], [13.405, 52.5], [13.405, 52.505], [13.4, 52.505], [13.4, 52.5]]]}`,
|
|
"properties": `{"leisure": "park", "name": "Test Park"}`,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
table: "osm_buildings",
|
|
data: []map[string]interface{}{
|
|
{
|
|
"id": "building-1", "osm_type": "way", "osm_id": "1001", "building_type": "office",
|
|
"geometry": `{"type": "Polygon", "coordinates": [[[13.4, 52.5], [13.41, 52.5], [13.41, 52.51], [13.4, 52.51], [13.4, 52.5]]]}`,
|
|
"properties": `{"building": "office", "name": "Test Office Building", "levels": 5}`,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
table: "osm_roads",
|
|
data: []map[string]interface{}{
|
|
{
|
|
"id": "road-1", "osm_type": "way", "osm_id": "2001", "road_type": "primary",
|
|
"geometry": `{"type": "LineString", "coordinates": [[13.4, 52.5], [13.42, 52.5]]}`,
|
|
"properties": `{"highway": "primary", "name": "Test Road", "surface": "asphalt"}`,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
table: "osm_green_spaces",
|
|
data: []map[string]interface{}{
|
|
{
|
|
"id": "park-1", "osm_type": "way", "osm_id": "3001", "green_space_type": "park",
|
|
"geometry": `{"type": "Polygon", "coordinates": [[[13.4, 52.5], [13.405, 52.5], [13.405, 52.505], [13.4, 52.505], [13.4, 52.5]]]}`,
|
|
"properties": `{"leisure": "park", "name": "Test Park", "area": "small"}`,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tableData := range testData {
|
|
for _, record := range tableData.data {
|
|
columns := []string{"id", "osm_type", "osm_id", "feature_type", "geometry", "properties"}
|
|
if tableData.table != "osm_features" {
|
|
switch tableData.table {
|
|
case "osm_buildings":
|
|
columns = []string{"id", "osm_type", "osm_id", "building_type", "geometry", "properties"}
|
|
case "osm_roads":
|
|
columns = []string{"id", "osm_type", "osm_id", "road_type", "geometry", "properties"}
|
|
case "osm_green_spaces":
|
|
columns = []string{"id", "osm_type", "osm_id", "green_space_type", "geometry", "properties"}
|
|
}
|
|
}
|
|
|
|
values := make([]interface{}, len(columns))
|
|
for i, col := range columns {
|
|
values[i] = record[col]
|
|
}
|
|
|
|
placeholders := "?, ?, ?, ?, ?, ?"
|
|
query := "INSERT INTO " + tableData.table + " (" + columns[0]
|
|
for i := 1; i < len(columns); i++ {
|
|
query += ", " + columns[i]
|
|
}
|
|
query += ") VALUES (" + placeholders[:len(placeholders)-len(", ?")] + placeholders[len(placeholders)-3:] + ")"
|
|
|
|
_, err := db.Exec(query, values...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (suite *GeographicalDataMigrationServiceTestSuite) TestNewGeographicalDataMigrationService() {
|
|
assert.NotNil(suite.T(), suite.migrationSvc)
|
|
}
|
|
|
|
func (suite *GeographicalDataMigrationServiceTestSuite) TestMigrateBuildingPolygons() {
|
|
progress, err := suite.migrationSvc.MigrateBuildingPolygons(context.Background())
|
|
assert.NoError(suite.T(), err)
|
|
assert.NotNil(suite.T(), progress)
|
|
assert.Equal(suite.T(), "Migrating Building Polygons", progress.CurrentOperation)
|
|
assert.Greater(suite.T(), progress.TotalRecords, 0)
|
|
|
|
// Verify buildings were migrated
|
|
buildings, err := suite.geoRepo.GetByType(context.Background(), domain.GeographicalFeatureTypeLandUse)
|
|
assert.NoError(suite.T(), err)
|
|
assert.Len(suite.T(), buildings, progress.Successful)
|
|
|
|
// Verify building data
|
|
if len(buildings) > 0 {
|
|
building := buildings[0]
|
|
assert.Equal(suite.T(), domain.GeographicalFeatureTypeLandUse, building.FeatureType)
|
|
assert.Equal(suite.T(), "way", building.OSMType)
|
|
assert.NotEmpty(suite.T(), building.Properties)
|
|
|
|
var props map[string]interface{}
|
|
err := json.Unmarshal(building.Properties, &props)
|
|
assert.NoError(suite.T(), err)
|
|
assert.Contains(suite.T(), props, "building")
|
|
assert.Contains(suite.T(), props, "name")
|
|
}
|
|
}
|
|
|
|
func (suite *GeographicalDataMigrationServiceTestSuite) TestMigrateRoadNetwork() {
|
|
progress, err := suite.migrationSvc.MigrateRoadNetwork(context.Background())
|
|
assert.NoError(suite.T(), err)
|
|
assert.NotNil(suite.T(), progress)
|
|
assert.Equal(suite.T(), "Migrating Road Network", progress.CurrentOperation)
|
|
assert.Greater(suite.T(), progress.TotalRecords, 0)
|
|
|
|
// Verify roads were migrated
|
|
roads, err := suite.geoRepo.GetByType(context.Background(), domain.GeographicalFeatureTypeRoad)
|
|
assert.NoError(suite.T(), err)
|
|
assert.Len(suite.T(), roads, progress.Successful)
|
|
|
|
// Verify road data
|
|
if len(roads) > 0 {
|
|
road := roads[0]
|
|
assert.Equal(suite.T(), domain.GeographicalFeatureTypeRoad, road.FeatureType)
|
|
assert.Equal(suite.T(), "way", road.OSMType)
|
|
|
|
var props map[string]interface{}
|
|
err := json.Unmarshal(road.Properties, &props)
|
|
assert.NoError(suite.T(), err)
|
|
assert.Contains(suite.T(), props, "highway")
|
|
}
|
|
}
|
|
|
|
func (suite *GeographicalDataMigrationServiceTestSuite) TestMigrateGreenSpaces() {
|
|
progress, err := suite.migrationSvc.MigrateGreenSpaces(context.Background())
|
|
assert.NoError(suite.T(), err)
|
|
assert.NotNil(suite.T(), progress)
|
|
assert.Equal(suite.T(), "Migrating Green Spaces", progress.CurrentOperation)
|
|
assert.Greater(suite.T(), progress.TotalRecords, 0)
|
|
|
|
// Verify green spaces were migrated
|
|
greenSpaces, err := suite.geoRepo.GetByType(context.Background(), domain.GeographicalFeatureTypeGreenSpace)
|
|
assert.NoError(suite.T(), err)
|
|
assert.Len(suite.T(), greenSpaces, progress.Successful)
|
|
|
|
// Verify green space data
|
|
if len(greenSpaces) > 0 {
|
|
greenSpace := greenSpaces[0]
|
|
assert.Equal(suite.T(), domain.GeographicalFeatureTypeGreenSpace, greenSpace.FeatureType)
|
|
assert.Equal(suite.T(), "way", greenSpace.OSMType)
|
|
|
|
var props map[string]interface{}
|
|
err := json.Unmarshal(greenSpace.Properties, &props)
|
|
assert.NoError(suite.T(), err)
|
|
assert.Contains(suite.T(), props, "leisure")
|
|
assert.Equal(suite.T(), "park", props["leisure"])
|
|
}
|
|
}
|
|
|
|
func (suite *GeographicalDataMigrationServiceTestSuite) TestGenerateMigrationStatistics() {
|
|
// Run migrations first
|
|
suite.migrationSvc.MigrateBuildingPolygons(context.Background())
|
|
suite.migrationSvc.MigrateRoadNetwork(context.Background())
|
|
suite.migrationSvc.MigrateGreenSpaces(context.Background())
|
|
|
|
stats, err := suite.migrationSvc.GetMigrationStatistics(context.Background())
|
|
assert.NoError(suite.T(), err)
|
|
assert.NotNil(suite.T(), stats)
|
|
|
|
// Verify expected statistics
|
|
assert.Contains(suite.T(), stats, "sites")
|
|
assert.Contains(suite.T(), stats, "green_space_total_area_km2")
|
|
assert.Contains(suite.T(), stats, "road_network")
|
|
|
|
// Check road network statistics structure
|
|
roadStats, ok := stats["road_network"].(map[string]interface{})
|
|
assert.True(suite.T(), ok)
|
|
assert.Contains(suite.T(), roadStats, "total_roads")
|
|
assert.Contains(suite.T(), roadStats, "total_length_km")
|
|
assert.Contains(suite.T(), roadStats, "avg_length_km")
|
|
assert.Contains(suite.T(), roadStats, "max_length_km")
|
|
}
|
|
|
|
func (suite *GeographicalDataMigrationServiceTestSuite) TestMigrationProgress_Tracking() {
|
|
progress, err := suite.migrationSvc.MigrateBuildingPolygons(context.Background())
|
|
assert.NoError(suite.T(), err)
|
|
|
|
// Verify progress tracking
|
|
assert.GreaterOrEqual(suite.T(), progress.ProcessedRecords, 0)
|
|
assert.GreaterOrEqual(suite.T(), progress.Successful, 0)
|
|
assert.GreaterOrEqual(suite.T(), progress.Failed, 0)
|
|
assert.Equal(suite.T(), progress.Successful+progress.Failed, progress.ProcessedRecords)
|
|
assert.GreaterOrEqual(suite.T(), progress.ProgressPercent, 0.0)
|
|
assert.LessOrEqual(suite.T(), progress.ProgressPercent, 100.0)
|
|
}
|
|
|
|
func (suite *GeographicalDataMigrationServiceTestSuite) TestMigrationWithSiteMatching() {
|
|
// Create a site that matches a building ID
|
|
site := &domain.Site{
|
|
ID: "building-1", // Same ID as test building
|
|
Name: "Matching Site",
|
|
Latitude: 52.505,
|
|
Longitude: 13.405,
|
|
SiteType: domain.SiteTypeIndustrial,
|
|
OwnerOrganizationID: "org-migration-test",
|
|
}
|
|
|
|
err := suite.siteRepo.Create(context.Background(), site)
|
|
suite.Require().NoError(err)
|
|
|
|
// Run building migration
|
|
progress, err := suite.migrationSvc.MigrateBuildingPolygons(context.Background())
|
|
assert.NoError(suite.T(), err)
|
|
assert.True(suite.T(), progress.Successful > 0)
|
|
|
|
// Verify the site still exists (geometry is stored at database level, not in struct)
|
|
updatedSite, err := suite.siteRepo.GetByID(context.Background(), "building-1")
|
|
assert.NoError(suite.T(), err)
|
|
assert.NotNil(suite.T(), updatedSite)
|
|
}
|
|
|
|
func (suite *GeographicalDataMigrationServiceTestSuite) TestClose() {
|
|
err := suite.migrationSvc.Close()
|
|
assert.NoError(suite.T(), err)
|
|
|
|
// Should be able to close multiple times without error
|
|
err = suite.migrationSvc.Close()
|
|
assert.NoError(suite.T(), err)
|
|
}
|
|
|
|
func (suite *GeographicalDataMigrationServiceTestSuite) TestMigrationErrorHandling() {
|
|
// Test with invalid SQLite path (service should handle this gracefully)
|
|
invalidSvc, err := service.NewGeographicalDataMigrationService(
|
|
suite.db, suite.geoRepo, suite.siteRepo, "/nonexistent/path.db",
|
|
)
|
|
assert.Error(suite.T(), err)
|
|
assert.Nil(suite.T(), invalidSvc)
|
|
}
|
|
|
|
func (suite *GeographicalDataMigrationServiceTestSuite) TestEmptyMigration() {
|
|
// Create a service with empty SQLite database
|
|
emptyDBPath := filepath.Join(suite.tempDir, "empty.db")
|
|
emptyDB, err := sql.Open("sqlite3", emptyDBPath)
|
|
suite.Require().NoError(err)
|
|
|
|
// Create empty table
|
|
_, err = emptyDB.Exec(`CREATE TABLE osm_features (id TEXT, osm_type TEXT, osm_id TEXT, feature_type TEXT, geometry TEXT, properties TEXT)`)
|
|
suite.Require().NoError(err)
|
|
emptyDB.Close()
|
|
|
|
emptySvc, err := service.NewGeographicalDataMigrationService(
|
|
suite.db, suite.geoRepo, suite.siteRepo, emptyDBPath,
|
|
)
|
|
suite.Require().NoError(err)
|
|
defer emptySvc.Close()
|
|
|
|
// Run migration on empty data
|
|
progress, err := emptySvc.MigrateBuildingPolygons(context.Background())
|
|
assert.NoError(suite.T(), err)
|
|
assert.Equal(suite.T(), 0, progress.TotalRecords)
|
|
assert.Equal(suite.T(), 0, progress.Successful)
|
|
assert.Equal(suite.T(), 0, progress.Failed)
|
|
assert.Equal(suite.T(), 100.0, progress.ProgressPercent)
|
|
}
|