53200cbc0d
Nuevas funciones Go con tests en tres dominios: - core: parse_cron_expr, next_cron_time, join_by_key, validate_struct_fields + tipo CronSchedule - datascience: pivot (tabla dinámica), diff_entities (comparación de entidades) - infra: http_get_json, http_post_json, http_download_file, cache_to_sqlite, cron_ticker Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
135 lines
2.6 KiB
Go
135 lines
2.6 KiB
Go
package infra
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func tempDB(t *testing.T) string {
|
|
t.Helper()
|
|
f, err := os.CreateTemp(t.TempDir(), "cache_*.db")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
f.Close()
|
|
return f.Name()
|
|
}
|
|
|
|
func TestCacheToSQLite_SetGet(t *testing.T) {
|
|
t.Run("Set/Get basico", func(t *testing.T) {
|
|
c, err := CacheToSQLite(tempDB(t), "default")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer c.Close()
|
|
|
|
payload, _ := json.Marshal(map[string]int{"x": 1})
|
|
if err := c.Set("foo", payload, 0); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, ok := c.Get("foo")
|
|
if !ok {
|
|
t.Fatal("expected cache hit")
|
|
}
|
|
var result map[string]int
|
|
json.Unmarshal(got, &result)
|
|
if result["x"] != 1 {
|
|
t.Errorf("got %v, want x=1", result)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestCacheToSQLite_TTLExpirado(t *testing.T) {
|
|
t.Run("TTL expirado", func(t *testing.T) {
|
|
c, err := CacheToSQLite(tempDB(t), "default")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer c.Close()
|
|
|
|
payload, _ := json.Marshal("hello")
|
|
c.Set("temp", payload, 50*time.Millisecond)
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
_, ok := c.Get("temp")
|
|
if ok {
|
|
t.Error("expected cache miss after TTL expiry")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestCacheToSQLite_GetOrSet(t *testing.T) {
|
|
t.Run("GetOrSet con factory", func(t *testing.T) {
|
|
c, err := CacheToSQLite(tempDB(t), "default")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer c.Close()
|
|
|
|
calls := 0
|
|
factory := func() ([]byte, error) {
|
|
calls++
|
|
return json.Marshal("computed")
|
|
}
|
|
|
|
v1, err := c.GetOrSet("k", factory, time.Minute)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
v2, err := c.GetOrSet("k", factory, time.Minute)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(v1) != string(v2) {
|
|
t.Errorf("v1=%s v2=%s, want equal", v1, v2)
|
|
}
|
|
if calls != 1 {
|
|
t.Errorf("factory called %d times, want 1", calls)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestCacheToSQLite_Concurrencia(t *testing.T) {
|
|
t.Run("Concurrencia (goroutines)", func(t *testing.T) {
|
|
c, err := CacheToSQLite(tempDB(t), "parallel")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer c.Close()
|
|
|
|
var wg sync.WaitGroup
|
|
errs := make(chan error, 40)
|
|
for i := 0; i < 20; i++ {
|
|
wg.Add(1)
|
|
go func(n int) {
|
|
defer wg.Done()
|
|
key := fmt.Sprintf("key_%d", n)
|
|
payload, _ := json.Marshal(n)
|
|
if err := c.Set(key, payload, 0); err != nil {
|
|
errs <- err
|
|
return
|
|
}
|
|
got, ok := c.Get(key)
|
|
if !ok {
|
|
errs <- fmt.Errorf("miss for key %s", key)
|
|
return
|
|
}
|
|
var val int
|
|
json.Unmarshal(got, &val)
|
|
if val != n {
|
|
errs <- fmt.Errorf("key %s: got %d want %d", key, val, n)
|
|
}
|
|
}(i)
|
|
}
|
|
wg.Wait()
|
|
close(errs)
|
|
for err := range errs {
|
|
t.Error(err)
|
|
}
|
|
})
|
|
}
|