Files
jety/unmarshal_test.go
Tai Groot 0c0fc0320c feat(unmarshal): add Unmarshal and UnmarshalKey methods
Add struct unmarshaling support via JSON round-trip:
- Unmarshal() unmarshals full combined config into a struct
- UnmarshalKey() unmarshals a specific key (supports dot notation)
- Package-level convenience functions for default manager
- Comprehensive tests covering JSON, TOML, YAML, nested keys, scalars
- Bump Go to 1.26.2
- Fix stale Delete reference in README API table
- Add .gitignore for cover.out
2026-04-13 07:36:10 +00:00

334 lines
6.7 KiB
Go

package jety
import (
"os"
"path/filepath"
"testing"
)
func TestUnmarshal(t *testing.T) {
type ServerConfig struct {
Host string `json:"host"`
Port int `json:"port"`
}
type AppConfig struct {
Name string `json:"name"`
Debug bool `json:"debug"`
Server ServerConfig `json:"server"`
}
dir := t.TempDir()
configFile := filepath.Join(dir, "config.json")
err := os.WriteFile(configFile, []byte(`{
"name": "myapp",
"debug": true,
"server": {
"host": "localhost",
"port": 8080
}
}`), 0644)
if err != nil {
t.Fatal(err)
}
cm := NewConfigManager()
cm.SetConfigFile(configFile)
if err := cm.SetConfigType("json"); err != nil {
t.Fatal(err)
}
if err := cm.ReadInConfig(); err != nil {
t.Fatal(err)
}
var cfg AppConfig
if err := cm.Unmarshal(&cfg); err != nil {
t.Fatal(err)
}
if cfg.Name != "myapp" {
t.Errorf("expected name 'myapp', got %q", cfg.Name)
}
if !cfg.Debug {
t.Error("expected debug to be true")
}
if cfg.Server.Host != "localhost" {
t.Errorf("expected host 'localhost', got %q", cfg.Server.Host)
}
if cfg.Server.Port != 8080 {
t.Errorf("expected port 8080, got %d", cfg.Server.Port)
}
}
func TestUnmarshalWithDefaults(t *testing.T) {
type Config struct {
Host string `json:"host"`
Port int `json:"port"`
Timeout int `json:"timeout"`
}
dir := t.TempDir()
configFile := filepath.Join(dir, "config.json")
err := os.WriteFile(configFile, []byte(`{
"host": "example.com"
}`), 0644)
if err != nil {
t.Fatal(err)
}
cm := NewConfigManager()
cm.SetDefault("port", 3000)
cm.SetDefault("timeout", 30)
cm.SetConfigFile(configFile)
if err := cm.SetConfigType("json"); err != nil {
t.Fatal(err)
}
if err := cm.ReadInConfig(); err != nil {
t.Fatal(err)
}
var cfg Config
if err := cm.Unmarshal(&cfg); err != nil {
t.Fatal(err)
}
if cfg.Host != "example.com" {
t.Errorf("expected host 'example.com', got %q", cfg.Host)
}
if cfg.Port != 3000 {
t.Errorf("expected port 3000, got %d", cfg.Port)
}
if cfg.Timeout != 30 {
t.Errorf("expected timeout 30, got %d", cfg.Timeout)
}
}
func TestUnmarshalKey(t *testing.T) {
type DatabaseConfig struct {
Host string `json:"host"`
Port int `json:"port"`
Name string `json:"name"`
}
dir := t.TempDir()
configFile := filepath.Join(dir, "config.json")
err := os.WriteFile(configFile, []byte(`{
"database": {
"host": "db.example.com",
"port": 5432,
"name": "mydb"
},
"cache": {
"host": "cache.example.com",
"port": 6379
}
}`), 0644)
if err != nil {
t.Fatal(err)
}
cm := NewConfigManager()
cm.SetConfigFile(configFile)
if err := cm.SetConfigType("json"); err != nil {
t.Fatal(err)
}
if err := cm.ReadInConfig(); err != nil {
t.Fatal(err)
}
var dbCfg DatabaseConfig
if err := cm.UnmarshalKey("database", &dbCfg); err != nil {
t.Fatal(err)
}
if dbCfg.Host != "db.example.com" {
t.Errorf("expected host 'db.example.com', got %q", dbCfg.Host)
}
if dbCfg.Port != 5432 {
t.Errorf("expected port 5432, got %d", dbCfg.Port)
}
if dbCfg.Name != "mydb" {
t.Errorf("expected name 'mydb', got %q", dbCfg.Name)
}
}
func TestUnmarshalKeyNotFound(t *testing.T) {
cm := NewConfigManager()
var target struct{}
err := cm.UnmarshalKey("nonexistent", &target)
if err == nil {
t.Error("expected error for missing key")
}
}
func TestUnmarshalKeyScalarValue(t *testing.T) {
cm := NewConfigManager()
cm.Set("count", 42)
var count int
if err := cm.UnmarshalKey("count", &count); err != nil {
t.Fatal(err)
}
if count != 42 {
t.Errorf("expected 42, got %d", count)
}
}
func TestUnmarshalKeyNestedDotNotation(t *testing.T) {
type Inner struct {
Value string `json:"value"`
}
dir := t.TempDir()
configFile := filepath.Join(dir, "config.json")
err := os.WriteFile(configFile, []byte(`{
"services": {
"api": {
"value": "hello"
}
}
}`), 0644)
if err != nil {
t.Fatal(err)
}
cm := NewConfigManager()
cm.SetConfigFile(configFile)
if err := cm.SetConfigType("json"); err != nil {
t.Fatal(err)
}
if err := cm.ReadInConfig(); err != nil {
t.Fatal(err)
}
var inner Inner
if err := cm.UnmarshalKey("services.api", &inner); err != nil {
t.Fatal(err)
}
if inner.Value != "hello" {
t.Errorf("expected 'hello', got %q", inner.Value)
}
}
func TestUnmarshalTOML(t *testing.T) {
type Config struct {
Title string `json:"title"`
Port int `json:"port"`
}
dir := t.TempDir()
configFile := filepath.Join(dir, "config.toml")
err := os.WriteFile(configFile, []byte(`
title = "My App"
port = 9090
`), 0644)
if err != nil {
t.Fatal(err)
}
cm := NewConfigManager()
cm.SetConfigFile(configFile)
if err := cm.SetConfigType("toml"); err != nil {
t.Fatal(err)
}
if err := cm.ReadInConfig(); err != nil {
t.Fatal(err)
}
var cfg Config
if err := cm.Unmarshal(&cfg); err != nil {
t.Fatal(err)
}
if cfg.Title != "My App" {
t.Errorf("expected title 'My App', got %q", cfg.Title)
}
if cfg.Port != 9090 {
t.Errorf("expected port 9090, got %d", cfg.Port)
}
}
func TestUnmarshalYAML(t *testing.T) {
type Config struct {
Name string `json:"name"`
Tags []string `json:"tags"`
Enabled bool `json:"enabled"`
}
dir := t.TempDir()
configFile := filepath.Join(dir, "config.yaml")
err := os.WriteFile(configFile, []byte(`
name: testapp
tags:
- web
- api
enabled: true
`), 0644)
if err != nil {
t.Fatal(err)
}
cm := NewConfigManager()
cm.SetConfigFile(configFile)
if err := cm.SetConfigType("yaml"); err != nil {
t.Fatal(err)
}
if err := cm.ReadInConfig(); err != nil {
t.Fatal(err)
}
var cfg Config
if err := cm.Unmarshal(&cfg); err != nil {
t.Fatal(err)
}
if cfg.Name != "testapp" {
t.Errorf("expected name 'testapp', got %q", cfg.Name)
}
if len(cfg.Tags) != 2 || cfg.Tags[0] != "web" || cfg.Tags[1] != "api" {
t.Errorf("expected tags [web api], got %v", cfg.Tags)
}
if !cfg.Enabled {
t.Error("expected enabled to be true")
}
}
func TestDefaultUnmarshal(t *testing.T) {
// Test package-level Unmarshal function
type Config struct {
Key string `json:"key"`
}
// Reset default manager
defaultConfigManager = NewConfigManager()
Set("key", "value")
var cfg Config
if err := Unmarshal(&cfg); err != nil {
t.Fatal(err)
}
if cfg.Key != "value" {
t.Errorf("expected 'value', got %q", cfg.Key)
}
// Restore
defaultConfigManager = NewConfigManager()
}
func TestDefaultUnmarshalKey(t *testing.T) {
defaultConfigManager = NewConfigManager()
Set("section", map[string]any{"field": "data"})
type Section struct {
Field string `json:"field"`
}
var sec Section
if err := UnmarshalKey("section", &sec); err != nil {
t.Fatal(err)
}
if sec.Field != "data" {
t.Errorf("expected 'data', got %q", sec.Field)
}
defaultConfigManager = NewConfigManager()
}