turash/bugulma/backend/internal/service/graph_traversal_service_test.go
Damir Mukimov 000eab4740
Major repository reorganization and missing backend endpoints implementation
Repository Structure:
- Move files from cluttered root directory into organized structure
- Create archive/ for archived data and scraper results
- Create bugulma/ for the complete application (frontend + backend)
- Create data/ for sample datasets and reference materials
- Create docs/ for comprehensive documentation structure
- Create scripts/ for utility scripts and API tools

Backend Implementation:
- Implement 3 missing backend endpoints identified in gap analysis:
  * GET /api/v1/organizations/{id}/matching/direct - Direct symbiosis matches
  * GET /api/v1/users/me/organizations - User organizations
  * POST /api/v1/proposals/{id}/status - Update proposal status
- Add complete proposal domain model, repository, and service layers
- Create database migration for proposals table
- Fix CLI server command registration issue

API Documentation:
- Add comprehensive proposals.md API documentation
- Update README.md with Users and Proposals API sections
- Document all request/response formats, error codes, and business rules

Code Quality:
- Follow existing Go backend architecture patterns
- Add proper error handling and validation
- Match frontend expected response schemas
- Maintain clean separation of concerns (handler -> service -> repository)
2025-11-25 06:01:16 +01:00

315 lines
9.6 KiB
Go

package service
import (
"context"
"testing"
"bugulma/backend/internal/domain"
"bugulma/backend/internal/graph"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
// MockGraphCalculator is a mock implementation of graph.Calculator
type MockGraphCalculator struct {
mock.Mock
}
func (m *MockGraphCalculator) FindResourceChains(ctx context.Context, resourceType domain.ResourceType, maxLength int, minValue float64) ([]*graph.ResourceChain, error) {
args := m.Called(ctx, resourceType, maxLength, minValue)
return args.Get(0).([]*graph.ResourceChain), args.Error(1)
}
func (m *MockGraphCalculator) FindSymbiosisNetworks(ctx context.Context, minOrgs int, maxSize int) ([]*graph.SymbiosisNetwork, error) {
args := m.Called(ctx, minOrgs, maxSize)
return args.Get(0).([]*graph.SymbiosisNetwork), args.Error(1)
}
func (m *MockGraphCalculator) FindOptimalResourcePaths(ctx context.Context, sourceOrgID, targetOrgID string, resourceType domain.ResourceType, maxHops int) ([]*graph.ResourceChain, error) {
args := m.Called(ctx, sourceOrgID, targetOrgID, resourceType, maxHops)
return args.Get(0).([]*graph.ResourceChain), args.Error(1)
}
func (m *MockGraphCalculator) AnalyzeNetworkCentrality(ctx context.Context) (map[string]*graph.CentralityMetrics, error) {
args := m.Called(ctx)
return args.Get(0).(map[string]*graph.CentralityMetrics), args.Error(1)
}
func (m *MockGraphCalculator) GetNetworkStatistics(ctx context.Context) (*graph.NetworkStatistics, error) {
args := m.Called(ctx)
return args.Get(0).(*graph.NetworkStatistics), args.Error(1)
}
func (m *MockGraphCalculator) FindCircularEconomyCycles(ctx context.Context) ([]*graph.CircularEconomyCycle, error) {
args := m.Called(ctx)
return args.Get(0).([]*graph.CircularEconomyCycle), args.Error(1)
}
// Helper functions for tests
func getStringProp(props map[string]interface{}, key string) string {
if val, ok := props[key]; ok && val != nil {
if str, ok := val.(string); ok {
return str
}
}
return ""
}
func getFloat64Prop(props map[string]interface{}, key string) float64 {
if val, ok := props[key]; ok && val != nil {
switch v := val.(type) {
case float64:
return v
case int64:
return float64(v)
case int:
return float64(v)
}
}
return 0.0
}
func TestNewGraphTraversalService(t *testing.T) {
mockCalculator := &MockGraphCalculator{}
service := NewGraphTraversalService(mockCalculator)
assert.NotNil(t, service)
assert.Equal(t, mockCalculator, service.calculator)
}
func TestGraphTraversalService_FindResourceChains(t *testing.T) {
mockCalculator := &MockGraphCalculator{}
service := NewGraphTraversalService(mockCalculator)
assert.NotNil(t, service)
// Test with mock expectations would go here
// Since Neo4j mocking is complex, we'll focus on integration tests
t.Skip("Integration test - requires Neo4j instance")
}
func TestGraphTraversalService_FindSymbiosisNetworks(t *testing.T) {
mockCalculator := &MockGraphCalculator{}
service := NewGraphTraversalService(mockCalculator)
assert.NotNil(t, service)
t.Skip("Integration test - requires Neo4j instance")
}
func TestGraphTraversalService_FindOptimalResourcePaths(t *testing.T) {
mockCalculator := &MockGraphCalculator{}
service := NewGraphTraversalService(mockCalculator)
assert.NotNil(t, service)
t.Skip("Integration test - requires Neo4j instance")
}
func TestGraphTraversalService_AnalyzeNetworkCentrality(t *testing.T) {
mockCalculator := &MockGraphCalculator{}
service := NewGraphTraversalService(mockCalculator)
assert.NotNil(t, service)
t.Skip("Integration test - requires Neo4j instance")
}
// Test data structures and helper functions
func TestResourceChain_Structure(t *testing.T) {
chain := &graph.ResourceChain{
ChainID: "test_chain_1",
ResourceType: domain.ResourceType("biowaste"),
TotalDistanceKm: 50.0,
TotalCost: 10000.0,
EnvironmentalImpact: 2000.0,
Circular: false,
Steps: []graph.ResourceChainStep{
{
StepNumber: 1,
SourceFlowID: "flow_1",
TargetFlowID: "flow_2",
SourceOrgID: "org_1",
SourceOrgName: "Company A",
TargetOrgID: "org_2",
TargetOrgName: "Company B",
DistanceKm: 25.0,
TransportCost: 500.0,
ProcessingCost: 2000.0,
ResourceQuantity: 100.0,
},
},
}
assert.Equal(t, "test_chain_1", chain.ChainID)
assert.Equal(t, domain.ResourceType("biowaste"), chain.ResourceType)
assert.Equal(t, 50.0, chain.TotalDistanceKm)
assert.Equal(t, 10000.0, chain.TotalCost)
assert.Equal(t, 2000.0, chain.EnvironmentalImpact)
assert.False(t, chain.Circular)
assert.Len(t, chain.Steps, 1)
step := chain.Steps[0]
assert.Equal(t, 1, step.StepNumber)
assert.Equal(t, "flow_1", step.SourceFlowID)
assert.Equal(t, "flow_2", step.TargetFlowID)
assert.Equal(t, "org_1", step.SourceOrgID)
assert.Equal(t, "Company A", step.SourceOrgName)
assert.Equal(t, "org_2", step.TargetOrgID)
assert.Equal(t, "Company B", step.TargetOrgName)
assert.Equal(t, 25.0, step.DistanceKm)
assert.Equal(t, 500.0, step.TransportCost)
assert.Equal(t, 2000.0, step.ProcessingCost)
assert.Equal(t, 100.0, step.ResourceQuantity)
}
func TestSymbiosisNetwork_Structure(t *testing.T) {
network := &graph.SymbiosisNetwork{
NetworkID: "network_1",
TotalValue: 50000.0,
EnvironmentalSavings: 10000.0,
NetworkEfficiency: 0.85,
GeographicSpan: 75.0,
Organizations: []graph.NetworkOrganization{
{
OrganizationID: "org_1",
Name: "Company A",
Role: "producer",
ResourceTypes: []string{"biowaste", "heat"},
Latitude: 48.8566,
Longitude: 2.3522,
},
{
OrganizationID: "org_2",
Name: "Company B",
Role: "processor",
ResourceTypes: []string{"biowaste", "materials"},
Latitude: 48.8566,
Longitude: 2.3522,
},
},
ResourceFlows: []graph.NetworkResourceFlow{
{
FlowID: "flow_1",
ResourceType: "biowaste",
SourceOrgID: "org_1",
TargetOrgID: "org_2",
Quantity: 1000.0,
Value: 25000.0,
DistanceKm: 5.0,
},
},
}
assert.Equal(t, "network_1", network.NetworkID)
assert.Equal(t, 50000.0, network.TotalValue)
assert.Equal(t, 10000.0, network.EnvironmentalSavings)
assert.Equal(t, 0.85, network.NetworkEfficiency)
assert.Equal(t, 75.0, network.GeographicSpan)
assert.Len(t, network.Organizations, 2)
assert.Len(t, network.ResourceFlows, 1)
org := network.Organizations[0]
assert.Equal(t, "org_1", org.OrganizationID)
assert.Equal(t, "Company A", org.Name)
assert.Equal(t, "producer", org.Role)
assert.Contains(t, org.ResourceTypes, "biowaste")
assert.Contains(t, org.ResourceTypes, "heat")
flow := network.ResourceFlows[0]
assert.Equal(t, "flow_1", flow.FlowID)
assert.Equal(t, "biowaste", flow.ResourceType)
assert.Equal(t, "org_1", flow.SourceOrgID)
assert.Equal(t, "org_2", flow.TargetOrgID)
assert.Equal(t, 1000.0, flow.Quantity)
assert.Equal(t, 25000.0, flow.Value)
assert.Equal(t, 5.0, flow.DistanceKm)
}
func TestNetworkOrganization_Structure(t *testing.T) {
org := graph.NetworkOrganization{
OrganizationID: "org_1",
Name: "Test Company",
Role: "facilitator",
ResourceTypes: []string{"heat", "water", "biowaste"},
Latitude: 52.5200,
Longitude: 13.4050,
}
assert.Equal(t, "org_1", org.OrganizationID)
assert.Equal(t, "Test Company", org.Name)
assert.Equal(t, "facilitator", org.Role)
assert.Len(t, org.ResourceTypes, 3)
assert.Contains(t, org.ResourceTypes, "heat")
assert.Contains(t, org.ResourceTypes, "water")
assert.Contains(t, org.ResourceTypes, "biowaste")
assert.Equal(t, 52.5200, org.Latitude)
assert.Equal(t, 13.4050, org.Longitude)
}
func TestNetworkResourceFlow_Structure(t *testing.T) {
flow := graph.NetworkResourceFlow{
FlowID: "flow_123",
ResourceType: "heat",
SourceOrgID: "org_a",
TargetOrgID: "org_b",
Quantity: 5000.0,
Value: 15000.0,
DistanceKm: 12.5,
}
assert.Equal(t, "flow_123", flow.FlowID)
assert.Equal(t, "heat", flow.ResourceType)
assert.Equal(t, "org_a", flow.SourceOrgID)
assert.Equal(t, "org_b", flow.TargetOrgID)
assert.Equal(t, 5000.0, flow.Quantity)
assert.Equal(t, 15000.0, flow.Value)
assert.Equal(t, 12.5, flow.DistanceKm)
}
// Test helper functions
func TestGetStringProp(t *testing.T) {
props := map[string]interface{}{
"name": "Test Company",
"id": "org_123",
"count": 42,
"nil_value": nil,
"empty_value": "",
}
assert.Equal(t, "Test Company", getStringProp(props, "name"))
assert.Equal(t, "org_123", getStringProp(props, "id"))
assert.Equal(t, "", getStringProp(props, "count")) // Wrong type
assert.Equal(t, "", getStringProp(props, "nil_value"))
assert.Equal(t, "", getStringProp(props, "empty_value"))
assert.Equal(t, "", getStringProp(props, "nonexistent"))
}
func TestGetFloat64Prop(t *testing.T) {
props := map[string]interface{}{
"value": 123.45,
"count": int64(42),
"quantity": 100,
"text": "not_a_number",
"nil_value": nil,
}
assert.Equal(t, 123.45, getFloat64Prop(props, "value"))
assert.Equal(t, 42.0, getFloat64Prop(props, "count"))
assert.Equal(t, 100.0, getFloat64Prop(props, "quantity"))
assert.Equal(t, 0.0, getFloat64Prop(props, "text"))
assert.Equal(t, 0.0, getFloat64Prop(props, "nil_value"))
assert.Equal(t, 0.0, getFloat64Prop(props, "nonexistent"))
}
// Integration test helpers
func createTestGraphTraversalService(t *testing.T) *GraphTraversalService {
// In a real test, this would connect to a test Neo4j instance
// For now, we'll create a mock that skips actual database operations
t.Skip("Skipping integration test - requires Neo4j test instance")
// Mock implementation would go here
return nil
}