github.com/kaptinlin/jsonschema@v0.4.6/default_funcs_test.go (about)

     1  package jsonschema
     2  
     3  import (
     4  	"fmt"
     5  	"math/rand"
     6  	"testing"
     7  	"time"
     8  )
     9  
    10  func TestDefaultFunc_DefaultNowFunc(t *testing.T) {
    11  	tests := []struct {
    12  		name     string
    13  		args     []any
    14  		wantType string
    15  	}{
    16  		{
    17  			name:     "default RFC3339",
    18  			args:     []any{},
    19  			wantType: "string",
    20  		},
    21  		{
    22  			name:     "custom format",
    23  			args:     []any{"2006-01-02"},
    24  			wantType: "string",
    25  		},
    26  		{
    27  			name:     "another custom format",
    28  			args:     []any{"15:04:05"},
    29  			wantType: "string",
    30  		},
    31  	}
    32  
    33  	for _, tt := range tests {
    34  		t.Run(tt.name, func(t *testing.T) {
    35  			result, err := DefaultNowFunc(tt.args...)
    36  			if err != nil {
    37  				t.Errorf("DefaultNowFunc() error = %v", err)
    38  				return
    39  			}
    40  
    41  			if _, ok := result.(string); !ok {
    42  				t.Errorf("DefaultNowFunc() result type = %T, want string", result)
    43  			}
    44  		})
    45  	}
    46  }
    47  
    48  func TestParseFunctionCall(t *testing.T) {
    49  	tests := []struct {
    50  		name    string
    51  		input   string
    52  		want    *FunctionCall
    53  		wantErr bool
    54  	}{
    55  		{
    56  			name:  "simple function no args",
    57  			input: "now()",
    58  			want:  &FunctionCall{Name: "now", Args: []any{}},
    59  		},
    60  		{
    61  			name:  "function with string arg",
    62  			input: "now(unix)",
    63  			want:  &FunctionCall{Name: "now", Args: []any{"unix"}},
    64  		},
    65  		{
    66  			name:  "function with multiple args",
    67  			input: "func(arg1, 42, 3.14)",
    68  			want:  &FunctionCall{Name: "func", Args: []any{"arg1", int64(42), float64(3.14)}},
    69  		},
    70  		{
    71  			name:  "not a function call",
    72  			input: "just a string",
    73  			want:  nil,
    74  		},
    75  		{
    76  			name:  "empty string",
    77  			input: "",
    78  			want:  nil,
    79  		},
    80  		{
    81  			name:  "invalid format",
    82  			input: "func(",
    83  			want:  nil,
    84  		},
    85  	}
    86  
    87  	for _, tt := range tests {
    88  		t.Run(tt.name, func(t *testing.T) {
    89  			got, err := parseFunctionCall(tt.input)
    90  			if (err != nil) != tt.wantErr {
    91  				t.Errorf("parseFunctionCall() error = %v, wantErr %v", err, tt.wantErr)
    92  				return
    93  			}
    94  
    95  			if tt.want == nil {
    96  				if got != nil {
    97  					t.Errorf("parseFunctionCall() = %v, want nil", got)
    98  				}
    99  				return
   100  			}
   101  
   102  			if got == nil {
   103  				t.Errorf("parseFunctionCall() = nil, want %v", tt.want)
   104  				return
   105  			}
   106  
   107  			if got.Name != tt.want.Name {
   108  				t.Errorf("parseFunctionCall() Name = %v, want %v", got.Name, tt.want.Name)
   109  			}
   110  
   111  			if len(got.Args) != len(tt.want.Args) {
   112  				t.Errorf("parseFunctionCall() Args length = %v, want %v", len(got.Args), len(tt.want.Args))
   113  				return
   114  			}
   115  
   116  			for i, arg := range got.Args {
   117  				if arg != tt.want.Args[i] {
   118  					t.Errorf("parseFunctionCall() Args[%d] = %v, want %v", i, arg, tt.want.Args[i])
   119  				}
   120  			}
   121  		})
   122  	}
   123  }
   124  
   125  func TestCompiler_RegisterDefaultFunc(t *testing.T) {
   126  	compiler := NewCompiler()
   127  
   128  	// Test registration
   129  	testFunc := func(args ...any) (any, error) {
   130  		return "test_result", nil
   131  	}
   132  
   133  	compiler.RegisterDefaultFunc("test", testFunc)
   134  
   135  	// Test retrieval
   136  	fn, exists := compiler.getDefaultFunc("test")
   137  	if !exists {
   138  		t.Error("Expected function to be registered")
   139  	}
   140  
   141  	result, err := fn()
   142  	if err != nil {
   143  		t.Errorf("Function call failed: %v", err)
   144  	}
   145  
   146  	if result != "test_result" {
   147  		t.Errorf("Function result = %v, want test_result", result)
   148  	}
   149  }
   150  
   151  // customIDFunc generates a simple random ID for testing
   152  func customIDFunc(args ...any) (any, error) {
   153  	return fmt.Sprintf("id_%d_%d", time.Now().Unix(), rand.Intn(10000)), nil
   154  }
   155  
   156  func TestDynamicDefaultValues_Integration(t *testing.T) {
   157  	compiler := NewCompiler()
   158  
   159  	// Register functions
   160  	compiler.RegisterDefaultFunc("now", DefaultNowFunc)
   161  	compiler.RegisterDefaultFunc("randomId", customIDFunc)
   162  
   163  	// Create schema with dynamic defaults
   164  	schemaJSON := `{
   165  		"type": "object",
   166  		"properties": {
   167  			"id": {
   168  				"type": "string",
   169  				"default": "randomId()"
   170  			},
   171  			"createdAt": {
   172  				"type": "string",
   173  				"default": "now()"
   174  			},
   175  			"updatedAt": {
   176  				"type": "string",
   177  				"default": "now(2006-01-02 15:04:05)"
   178  			},
   179  			"status": {
   180  				"type": "string",
   181  				"default": "active"
   182  			},
   183  			"version": {
   184  				"type": "string",
   185  				"default": "unregistered_func()"
   186  			}
   187  		}
   188  	}`
   189  
   190  	schema, err := compiler.Compile([]byte(schemaJSON))
   191  	if err != nil {
   192  		t.Fatalf("Failed to compile schema: %v", err)
   193  	}
   194  
   195  	// Test with partial data
   196  	inputData := map[string]interface{}{
   197  		"status": "pending",
   198  	}
   199  
   200  	var result map[string]interface{}
   201  	err = schema.Unmarshal(&result, inputData)
   202  	if err != nil {
   203  		t.Fatalf("Failed to unmarshal: %v", err)
   204  	}
   205  
   206  	// Verify results
   207  	if result["status"] != "pending" {
   208  		t.Errorf("status = %v, want pending", result["status"])
   209  	}
   210  
   211  	// Check that random ID was generated
   212  	if id, ok := result["id"].(string); !ok || len(id) == 0 {
   213  		t.Errorf("id should be a non-empty string, got %v", result["id"])
   214  	} else if id[:3] != "id_" {
   215  		t.Errorf("id should start with 'id_', got %v", id)
   216  	}
   217  
   218  	// Check that timestamp was generated
   219  	if createdAt, ok := result["createdAt"].(string); !ok || len(createdAt) == 0 {
   220  		t.Errorf("createdAt should be a non-empty string, got %v", result["createdAt"])
   221  	}
   222  
   223  	// Check that updated timestamp was generated
   224  	if updatedAt, ok := result["updatedAt"].(string); !ok || len(updatedAt) == 0 {
   225  		t.Errorf("updatedAt should be a non-empty string, got %v", result["updatedAt"])
   226  	}
   227  
   228  	// Check that unregistered function falls back to literal
   229  	if version, ok := result["version"].(string); !ok || version != "unregistered_func()" {
   230  		t.Errorf("version should be literal string 'unregistered_func()', got %v", result["version"])
   231  	}
   232  
   233  	t.Logf("Result: %+v", result)
   234  }
   235  
   236  func TestSchemaSetCompiler(t *testing.T) {
   237  	compiler := NewCompiler()
   238  	compiler.RegisterDefaultFunc("test", func(args ...any) (any, error) {
   239  		return "test_value", nil
   240  	})
   241  
   242  	schema := Object(
   243  		Prop("field", String(Default("test()"))),
   244  	).SetCompiler(compiler)
   245  
   246  	data := map[string]interface{}{}
   247  	var result map[string]interface{}
   248  	err := schema.Unmarshal(&result, data)
   249  	if err != nil {
   250  		t.Errorf("Unmarshal() error = %v", err)
   251  		return
   252  	}
   253  	if result["field"] != "test_value" {
   254  		t.Errorf("result['field'] = %v, want test_value", result["field"])
   255  	}
   256  }
   257  
   258  func TestSchemaCompilerIsolation(t *testing.T) {
   259  	compiler1 := NewCompiler()
   260  	compiler1.RegisterDefaultFunc("func1", func(args ...any) (any, error) {
   261  		return "value1", nil
   262  	})
   263  
   264  	compiler2 := NewCompiler()
   265  	compiler2.RegisterDefaultFunc("func2", func(args ...any) (any, error) {
   266  		return "value2", nil
   267  	})
   268  
   269  	schema1 := Object(
   270  		Prop("field1", String(Default("func1()"))),
   271  	).SetCompiler(compiler1)
   272  
   273  	schema2 := Object(
   274  		Prop("field2", String(Default("func2()"))),
   275  	).SetCompiler(compiler2)
   276  
   277  	// Verify isolation
   278  	data1 := map[string]interface{}{}
   279  	data2 := map[string]interface{}{}
   280  	var result1, result2 map[string]interface{}
   281  	err := schema1.Unmarshal(&result1, data1)
   282  	if err != nil {
   283  		t.Errorf("schema1.Unmarshal() error = %v", err)
   284  		return
   285  	}
   286  	err = schema2.Unmarshal(&result2, data2)
   287  	if err != nil {
   288  		t.Errorf("schema2.Unmarshal() error = %v", err)
   289  		return
   290  	}
   291  
   292  	if result1["field1"] != "value1" {
   293  		t.Errorf("result1['field1'] = %v, want value1", result1["field1"])
   294  	}
   295  	if result2["field2"] != "value2" {
   296  		t.Errorf("result2['field2'] = %v, want value2", result2["field2"])
   297  	}
   298  }
   299  
   300  func TestSchemaSetCompilerChaining(t *testing.T) {
   301  	compiler := NewCompiler()
   302  	compiler.RegisterDefaultFunc("test", func(args ...any) (any, error) {
   303  		return "chained", nil
   304  	})
   305  
   306  	// Test method chaining
   307  	schema := Object(
   308  		Prop("field", String(Default("test()"))),
   309  	).SetCompiler(compiler)
   310  
   311  	data := map[string]interface{}{}
   312  	var result map[string]interface{}
   313  	err := schema.Unmarshal(&result, data)
   314  	if err != nil {
   315  		t.Errorf("Unmarshal() error = %v", err)
   316  		return
   317  	}
   318  	if result["field"] != "chained" {
   319  		t.Errorf("result['field'] = %v, want chained", result["field"])
   320  	}
   321  }
   322  
   323  func TestSchemaCompilerInheritance(t *testing.T) {
   324  	compiler := NewCompiler()
   325  	compiler.RegisterDefaultFunc("test", func(args ...any) (any, error) {
   326  		return "inherited", nil
   327  	})
   328  
   329  	// Parent Schema sets Compiler, child Schema should inherit
   330  	schema := Object(
   331  		Prop("child", String(Default("test()"))), // Child Schema doesn't set compiler
   332  	).SetCompiler(compiler) // Only set on parent Schema
   333  
   334  	data := map[string]interface{}{}
   335  	var result map[string]interface{}
   336  	err := schema.Unmarshal(&result, data)
   337  	if err != nil {
   338  		t.Errorf("Unmarshal() error = %v", err)
   339  		return
   340  	}
   341  	if result["child"] != "inherited" {
   342  		t.Errorf("result['child'] = %v, want inherited", result["child"])
   343  	}
   344  }