Browse Source

add complex translations test to engine

pull/1/head
Stephanie Gredell 7 months ago
parent
commit
f8d5b56171
  1. 75
      internal/simulation/engine.go
  2. 34
      internal/simulation/engine_test.go
  3. 1
      internal/simulation/testdata/complex_design.json

75
internal/simulation/engine.go

@ -60,10 +60,10 @@ func NewEngineFromDesign(design design.Design, duration int, tickMs int) *Engine
case "cache": case "cache":
simNode = &CacheNode{ simNode = &CacheNode{
ID: n.ID, ID: n.ID,
Label: n.Props["label"].(string), Label: asString(n.Props["label"]),
CacheTTL: int(n.Props["cacheTTL"].(float64)), CacheTTL: int(asFloat64(n.Props["cacheTTL"])),
MaxEntries: int(n.Props["maxEntries"].(float64)), MaxEntries: int(asFloat64(n.Props["maxEntries"])),
EvictionPolicy: n.Props["evictionPolicy"].(string), EvictionPolicy: asString(n.Props["evictionPolicy"]),
CurrentLoad: 0, CurrentLoad: 0,
Queue: []*Request{}, Queue: []*Request{},
Cache: make(map[string]CacheEntry), Cache: make(map[string]CacheEntry),
@ -72,20 +72,20 @@ func NewEngineFromDesign(design design.Design, duration int, tickMs int) *Engine
case "database": case "database":
simNode = &DatabaseNode{ simNode = &DatabaseNode{
ID: n.ID, ID: n.ID,
Label: n.Props["label"].(string), Label: asString(n.Props["label"]),
Replication: int(n.Props["replication"].(float64)), Replication: int(asFloat64(n.Props["replication"])),
Queue: []*Request{}, Queue: []*Request{},
Alive: true, Alive: true,
} }
case "cdn": case "cdn":
simNode = &CDNNode{ simNode = &CDNNode{
ID: n.ID, ID: n.ID,
Label: n.Props["label"].(string), Label: asString(n.Props["label"]),
TTL: int(n.Props["ttl"].(float64)), TTL: int(asFloat64(n.Props["ttl"])),
GeoReplication: n.Props["geoReplication"].(string), GeoReplication: asString(n.Props["geoReplication"]),
CachingStrategy: n.Props["cachingStrategy"].(string), CachingStrategy: asString(n.Props["cachingStrategy"]),
Compression: n.Props["compression"].(string), Compression: asString(n.Props["compression"]),
HTTP2: n.Props["http2"].(string), HTTP2: asString(n.Props["http2"]),
Queue: []*Request{}, Queue: []*Request{},
Alive: true, Alive: true,
output: []*Request{}, output: []*Request{},
@ -94,9 +94,9 @@ func NewEngineFromDesign(design design.Design, duration int, tickMs int) *Engine
case "messageQueue": case "messageQueue":
simNode = &MessageQueueNode{ simNode = &MessageQueueNode{
ID: n.ID, ID: n.ID,
Label: n.Props["label"].(string), Label: asString(n.Props["label"]),
QueueSize: int(n.Props["maxSize"].(float64)), QueueSize: int(asFloat64(n.Props["maxSize"])),
MessageTTL: int(n.Props["retentionSeconds"].(float64)), MessageTTL: int(asFloat64(n.Props["retentionSeconds"])),
DeadLetter: false, DeadLetter: false,
Queue: []*Request{}, Queue: []*Request{},
Alive: true, Alive: true,
@ -104,9 +104,9 @@ func NewEngineFromDesign(design design.Design, duration int, tickMs int) *Engine
case "microservice": case "microservice":
simNode = &MicroserviceNode{ simNode = &MicroserviceNode{
ID: n.ID, ID: n.ID,
Label: n.Props["label"].(string), Label: asString(n.Props["label"]),
APIEndpoint: n.Props["apiVersion"].(string), APIEndpoint: asString(n.Props["apiVersion"]),
RateLimit: int(n.Props["rpsCapacity"].(float64)), RateLimit: int(asFloat64(n.Props["rpsCapacity"])),
CircuitBreaker: true, CircuitBreaker: true,
Queue: []*Request{}, Queue: []*Request{},
CircuitState: "closed", CircuitState: "closed",
@ -115,9 +115,9 @@ func NewEngineFromDesign(design design.Design, duration int, tickMs int) *Engine
case "third party service": case "third party service":
simNode = &ThirdPartyServiceNode{ simNode = &ThirdPartyServiceNode{
ID: n.ID, ID: n.ID,
Label: n.Props["label"].(string), Label: asString(n.Props["label"]),
APIEndpoint: n.Props["provider"].(string), APIEndpoint: asString(n.Props["provider"]),
RateLimit: int(n.Props["latency"].(float64)), RateLimit: int(asFloat64(n.Props["latency"])),
RetryPolicy: "exponential", RetryPolicy: "exponential",
Queue: []*Request{}, Queue: []*Request{},
Alive: true, Alive: true,
@ -125,20 +125,20 @@ func NewEngineFromDesign(design design.Design, duration int, tickMs int) *Engine
case "data pipeline": case "data pipeline":
simNode = &DataPipelineNode{ simNode = &DataPipelineNode{
ID: n.ID, ID: n.ID,
Label: n.Props["label"].(string), Label: asString(n.Props["label"]),
BatchSize: int(n.Props["batchSize"].(float64)), BatchSize: int(asFloat64(n.Props["batchSize"])),
Transformation: n.Props["transformation"].(string), Transformation: asString(n.Props["transformation"]),
Queue: []*Request{}, Queue: []*Request{},
Alive: true, Alive: true,
} }
case "monitoring/alerting": case "monitoring/alerting":
simNode = &MonitoringNode{ simNode = &MonitoringNode{
ID: n.ID, ID: n.ID,
Label: n.Props["label"].(string), Label: asString(n.Props["label"]),
Tool: n.Props["tool"].(string), Tool: asString(n.Props["tool"]),
AlertMetric: n.Props["metric"].(string), AlertMetric: asString(n.Props["metric"]),
ThresholdValue: int(n.Props["threshold"].(float64)), ThresholdValue: int(asFloat64(n.Props["threshold"])),
ThresholdUnit: n.Props["unit"].(string), ThresholdUnit: asString(n.Props["unit"]),
Queue: []*Request{}, Queue: []*Request{},
Alive: true, Alive: true,
} }
@ -243,3 +243,20 @@ func shouldInject(tick int) bool {
func generateRequestID(tick int) string { func generateRequestID(tick int) string {
return fmt.Sprintf("req-%d-%d", tick, rand.Intn(1000)) return fmt.Sprintf("req-%d-%d", tick, rand.Intn(1000))
} }
func asFloat64(v interface{}) float64 {
if v == nil {
return 0
}
return v.(float64)
}
func asString(v interface{}) string {
s, ok := v.(string)
if !ok {
return ""
}
return s
}

34
internal/simulation/engine_test.go

@ -1,8 +1,11 @@
package simulation package simulation
import ( import (
"os"
"path/filepath"
"testing" "testing"
"encoding/json"
"systemdesigngame/internal/design" "systemdesigngame/internal/design"
) )
@ -53,3 +56,34 @@ func TestNewEngineFromDesign(t *testing.T) {
} }
} }
func TestComplexSimulationRun(t *testing.T) {
filePath := filepath.Join("testdata", "complex_design.json")
data, err := os.ReadFile(filePath)
if err != nil {
t.Fatalf("Failed to read JSON file: %v", err)
}
var d design.Design
if err := json.Unmarshal([]byte(data), &d); err != nil {
t.Fatalf("Failed to unmarshal JSON: %v", err)
}
engine := NewEngineFromDesign(d, 10, 100)
if engine == nil {
t.Fatal("Engine should not be nil")
}
engine.Run()
if len(engine.Timeline) == 0 {
t.Fatal("Expected timeline snapshots after Run, got none")
}
// Optional: check that some nodes received or emitted requests
for id, node := range engine.Nodes {
if len(node.Emit()) > 0 {
t.Logf("Node %s has activity", id)
}
}
}

1
internal/simulation/testdata/complex_design.json vendored

@ -0,0 +1 @@
{"nodes":[{"id":"node-1","type":"loadBalancer","position":{"x":-4,"y":0},"props":{"label":"Load Balancer","algorithm":"round-robin"}},{"id":"node-2","type":"webserver","position":{"x":-1,"y":0},"props":{"label":"Web Server","instanceSize":"medium"}},{"id":"node-3","type":"database","position":{"x":177,"y":-176},"props":{"label":"Database","replication":1}},{"id":"node-4","type":"cache","position":{"x":204,"y":-78},"props":{"label":"Cache","cacheTTL":60,"maxEntries":100000,"evictionPolicy":"LRU"}},{"id":"node-5","type":"messageQueue","position":{"x":0,"y":0},"props":{"label":"MQ","maxSize":10000,"retentionSeconds":600}},{"id":"node-6","type":"cdn","position":{"x":20,"y":69},"props":{"label":"CDN","ttl":3600,"geoReplication":"global","cachingStrategy":"cache-first","compression":"brotli","http2":"enabled"}},{"id":"node-7","type":"microservice","position":{"x":0,"y":0},"props":{"label":"Service","instanceCount":3,"instanceSize":"medium","scalingStrategy":"auto","apiVersion":"v1"}},{"id":"node-8","type":"data pipeline","position":{"x":-453,"y":-121},"props":{"label":"pipeline","batchSize":500,"transformation":"map"}},{"id":"node-9","type":"monitoring/alerting","position":{"x":0,"y":0},"props":{"label":"monitor","tool":"Prometheus","alertThreshold":80}},{"id":"node-10","type":"third party service","position":{"x":0,"y":0},"props":{"label":"third party service","provider":"Stripe","latency":200}}],"connections":[{"source":"node-0","target":"node-1","label":"Read traffic","direction":"forward","protocol":"HTTP","tls":false,"capacity":1000},{"source":"node-1","target":"node-2","label":"Read traffic","direction":"forward","protocol":"HTTP","tls":false,"capacity":1000},{"source":"node-2","target":"node-3","label":"Read traffic","direction":"forward","protocol":"HTTP","tls":false,"capacity":1000},{"source":"node-3","target":"node-4","label":"Read traffic","direction":"forward","protocol":"HTTP","tls":false,"capacity":1000},{"source":"node-4","target":"node-5","label":"Read traffic","direction":"forward","protocol":"HTTP","tls":false,"capacity":1000},{"source":"node-5","target":"node-6","label":"Read traffic","direction":"forward","protocol":"HTTP","tls":false,"capacity":1000},{"source":"node-6","target":"node-7","label":"Read traffic","direction":"forward","protocol":"HTTP","tls":false,"capacity":1000},{"source":"node-8","target":"node-7","label":"Read traffic","direction":"forward","protocol":"HTTP","tls":false,"capacity":1000},{"source":"node-8","target":"node-10","label":"Read traffic","direction":"forward","protocol":"HTTP","tls":false,"capacity":1000},{"source":"node-10","target":"node-9","label":"Read traffic","direction":"forward","protocol":"HTTP","tls":false,"capacity":1000},{"source":"node-9","target":"node-0","label":"Read traffic","direction":"forward","protocol":"HTTP","tls":false,"capacity":1000}]}
Loading…
Cancel
Save