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 @@ -60,10 +60,10 @@ func NewEngineFromDesign(design design.Design, duration int, tickMs int) *Engine
case "cache":
simNode = &CacheNode{
ID: n.ID,
Label: n.Props["label"].(string),
CacheTTL: int(n.Props["cacheTTL"].(float64)),
MaxEntries: int(n.Props["maxEntries"].(float64)),
EvictionPolicy: n.Props["evictionPolicy"].(string),
Label: asString(n.Props["label"]),
CacheTTL: int(asFloat64(n.Props["cacheTTL"])),
MaxEntries: int(asFloat64(n.Props["maxEntries"])),
EvictionPolicy: asString(n.Props["evictionPolicy"]),
CurrentLoad: 0,
Queue: []*Request{},
Cache: make(map[string]CacheEntry),
@ -72,20 +72,20 @@ func NewEngineFromDesign(design design.Design, duration int, tickMs int) *Engine @@ -72,20 +72,20 @@ func NewEngineFromDesign(design design.Design, duration int, tickMs int) *Engine
case "database":
simNode = &DatabaseNode{
ID: n.ID,
Label: n.Props["label"].(string),
Replication: int(n.Props["replication"].(float64)),
Label: asString(n.Props["label"]),
Replication: int(asFloat64(n.Props["replication"])),
Queue: []*Request{},
Alive: true,
}
case "cdn":
simNode = &CDNNode{
ID: n.ID,
Label: n.Props["label"].(string),
TTL: int(n.Props["ttl"].(float64)),
GeoReplication: n.Props["geoReplication"].(string),
CachingStrategy: n.Props["cachingStrategy"].(string),
Compression: n.Props["compression"].(string),
HTTP2: n.Props["http2"].(string),
Label: asString(n.Props["label"]),
TTL: int(asFloat64(n.Props["ttl"])),
GeoReplication: asString(n.Props["geoReplication"]),
CachingStrategy: asString(n.Props["cachingStrategy"]),
Compression: asString(n.Props["compression"]),
HTTP2: asString(n.Props["http2"]),
Queue: []*Request{},
Alive: true,
output: []*Request{},
@ -94,9 +94,9 @@ func NewEngineFromDesign(design design.Design, duration int, tickMs int) *Engine @@ -94,9 +94,9 @@ func NewEngineFromDesign(design design.Design, duration int, tickMs int) *Engine
case "messageQueue":
simNode = &MessageQueueNode{
ID: n.ID,
Label: n.Props["label"].(string),
QueueSize: int(n.Props["maxSize"].(float64)),
MessageTTL: int(n.Props["retentionSeconds"].(float64)),
Label: asString(n.Props["label"]),
QueueSize: int(asFloat64(n.Props["maxSize"])),
MessageTTL: int(asFloat64(n.Props["retentionSeconds"])),
DeadLetter: false,
Queue: []*Request{},
Alive: true,
@ -104,9 +104,9 @@ func NewEngineFromDesign(design design.Design, duration int, tickMs int) *Engine @@ -104,9 +104,9 @@ func NewEngineFromDesign(design design.Design, duration int, tickMs int) *Engine
case "microservice":
simNode = &MicroserviceNode{
ID: n.ID,
Label: n.Props["label"].(string),
APIEndpoint: n.Props["apiVersion"].(string),
RateLimit: int(n.Props["rpsCapacity"].(float64)),
Label: asString(n.Props["label"]),
APIEndpoint: asString(n.Props["apiVersion"]),
RateLimit: int(asFloat64(n.Props["rpsCapacity"])),
CircuitBreaker: true,
Queue: []*Request{},
CircuitState: "closed",
@ -115,9 +115,9 @@ func NewEngineFromDesign(design design.Design, duration int, tickMs int) *Engine @@ -115,9 +115,9 @@ func NewEngineFromDesign(design design.Design, duration int, tickMs int) *Engine
case "third party service":
simNode = &ThirdPartyServiceNode{
ID: n.ID,
Label: n.Props["label"].(string),
APIEndpoint: n.Props["provider"].(string),
RateLimit: int(n.Props["latency"].(float64)),
Label: asString(n.Props["label"]),
APIEndpoint: asString(n.Props["provider"]),
RateLimit: int(asFloat64(n.Props["latency"])),
RetryPolicy: "exponential",
Queue: []*Request{},
Alive: true,
@ -125,20 +125,20 @@ func NewEngineFromDesign(design design.Design, duration int, tickMs int) *Engine @@ -125,20 +125,20 @@ func NewEngineFromDesign(design design.Design, duration int, tickMs int) *Engine
case "data pipeline":
simNode = &DataPipelineNode{
ID: n.ID,
Label: n.Props["label"].(string),
BatchSize: int(n.Props["batchSize"].(float64)),
Transformation: n.Props["transformation"].(string),
Label: asString(n.Props["label"]),
BatchSize: int(asFloat64(n.Props["batchSize"])),
Transformation: asString(n.Props["transformation"]),
Queue: []*Request{},
Alive: true,
}
case "monitoring/alerting":
simNode = &MonitoringNode{
ID: n.ID,
Label: n.Props["label"].(string),
Tool: n.Props["tool"].(string),
AlertMetric: n.Props["metric"].(string),
ThresholdValue: int(n.Props["threshold"].(float64)),
ThresholdUnit: n.Props["unit"].(string),
Label: asString(n.Props["label"]),
Tool: asString(n.Props["tool"]),
AlertMetric: asString(n.Props["metric"]),
ThresholdValue: int(asFloat64(n.Props["threshold"])),
ThresholdUnit: asString(n.Props["unit"]),
Queue: []*Request{},
Alive: true,
}
@ -243,3 +243,20 @@ func shouldInject(tick int) bool { @@ -243,3 +243,20 @@ func shouldInject(tick int) bool {
func generateRequestID(tick int) string {
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 @@ @@ -1,8 +1,11 @@
package simulation
import (
"os"
"path/filepath"
"testing"
"encoding/json"
"systemdesigngame/internal/design"
)
@ -53,3 +56,34 @@ func TestNewEngineFromDesign(t *testing.T) { @@ -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 @@ @@ -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