composition_test.go raw

   1  package policy
   2  
   3  import (
   4  	"encoding/json"
   5  	"testing"
   6  )
   7  
   8  // TestValidateOwnerPolicyUpdate tests owner-specific validation
   9  func TestValidateOwnerPolicyUpdate(t *testing.T) {
  10  	// Create a base policy
  11  	basePolicy := &P{
  12  		DefaultPolicy: "allow",
  13  		Owners:        []string{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
  14  		PolicyAdmins:  []string{"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"},
  15  	}
  16  
  17  	tests := []struct {
  18  		name        string
  19  		newPolicy   string
  20  		expectError bool
  21  		errorMsg    string
  22  	}{
  23  		{
  24  			name: "valid owner update with non-empty owners",
  25  			newPolicy: `{
  26  				"default_policy": "deny",
  27  				"owners": ["cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"],
  28  				"policy_admins": ["dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"]
  29  			}`,
  30  			expectError: false,
  31  		},
  32  		{
  33  			name: "invalid - empty owners list",
  34  			newPolicy: `{
  35  				"default_policy": "deny",
  36  				"owners": [],
  37  				"policy_admins": ["dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"]
  38  			}`,
  39  			expectError: true,
  40  			errorMsg:    "owners list cannot be empty",
  41  		},
  42  		{
  43  			name: "invalid - missing owners field",
  44  			newPolicy: `{
  45  				"default_policy": "deny",
  46  				"policy_admins": ["dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"]
  47  			}`,
  48  			expectError: true,
  49  			errorMsg:    "owners list cannot be empty",
  50  		},
  51  		{
  52  			name: "invalid - bad owner pubkey format",
  53  			newPolicy: `{
  54  				"default_policy": "deny",
  55  				"owners": ["not-a-valid-pubkey"]
  56  			}`,
  57  			expectError: true,
  58  			errorMsg:    "invalid owner pubkey",
  59  		},
  60  		{
  61  			name: "valid - owner can add multiple owners",
  62  			newPolicy: `{
  63  				"default_policy": "deny",
  64  				"owners": [
  65  					"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  66  					"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
  67  				]
  68  			}`,
  69  			expectError: false,
  70  		},
  71  	}
  72  
  73  	for _, tt := range tests {
  74  		t.Run(tt.name, func(t *testing.T) {
  75  			err := basePolicy.ValidateOwnerPolicyUpdate([]byte(tt.newPolicy))
  76  			if tt.expectError {
  77  				if err == nil {
  78  					t.Errorf("expected error containing %q, got nil", tt.errorMsg)
  79  				} else if tt.errorMsg != "" && !containsSubstring(err.Error(), tt.errorMsg) {
  80  					t.Errorf("expected error containing %q, got %q", tt.errorMsg, err.Error())
  81  				}
  82  			} else {
  83  				if err != nil {
  84  					t.Errorf("unexpected error: %v", err)
  85  				}
  86  			}
  87  		})
  88  	}
  89  }
  90  
  91  // TestValidatePolicyAdminUpdate tests policy admin validation
  92  func TestValidatePolicyAdminUpdate(t *testing.T) {
  93  	// Create a base policy with known owners and admins
  94  	ownerPubkey := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
  95  	adminPubkey := "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
  96  	allowedPubkey := "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
  97  
  98  	baseJSON := `{
  99  		"default_policy": "allow",
 100  		"owners": ["` + ownerPubkey + `"],
 101  		"policy_admins": ["` + adminPubkey + `"],
 102  		"kind": {
 103  			"whitelist": [1, 3, 7]
 104  		},
 105  		"rules": {
 106  			"1": {
 107  				"description": "Text notes",
 108  				"write_allow": ["` + allowedPubkey + `"],
 109  				"size_limit": 10000
 110  			}
 111  		}
 112  	}`
 113  
 114  	basePolicy := &P{}
 115  	if err := json.Unmarshal([]byte(baseJSON), basePolicy); err != nil {
 116  		t.Fatalf("failed to create base policy: %v", err)
 117  	}
 118  
 119  	adminPubkeyBin := make([]byte, 32)
 120  	for i := range adminPubkeyBin {
 121  		adminPubkeyBin[i] = 0xbb
 122  	}
 123  
 124  	tests := []struct {
 125  		name        string
 126  		newPolicy   string
 127  		expectError bool
 128  		errorMsg    string
 129  	}{
 130  		{
 131  			name: "valid - policy admin can extend write_allow",
 132  			newPolicy: `{
 133  				"default_policy": "allow",
 134  				"owners": ["` + ownerPubkey + `"],
 135  				"policy_admins": ["` + adminPubkey + `"],
 136  				"kind": {
 137  					"whitelist": [1, 3, 7]
 138  				},
 139  				"rules": {
 140  					"1": {
 141  						"description": "Text notes",
 142  						"write_allow": ["` + allowedPubkey + `", "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"],
 143  						"size_limit": 10000
 144  					}
 145  				}
 146  			}`,
 147  			expectError: false,
 148  		},
 149  		{
 150  			name: "valid - policy admin can add to kind whitelist",
 151  			newPolicy: `{
 152  				"default_policy": "allow",
 153  				"owners": ["` + ownerPubkey + `"],
 154  				"policy_admins": ["` + adminPubkey + `"],
 155  				"kind": {
 156  					"whitelist": [1, 3, 7, 30023]
 157  				},
 158  				"rules": {
 159  					"1": {
 160  						"description": "Text notes",
 161  						"write_allow": ["` + allowedPubkey + `"],
 162  						"size_limit": 10000
 163  					}
 164  				}
 165  			}`,
 166  			expectError: false,
 167  		},
 168  		{
 169  			name: "valid - policy admin can increase size limit",
 170  			newPolicy: `{
 171  				"default_policy": "allow",
 172  				"owners": ["` + ownerPubkey + `"],
 173  				"policy_admins": ["` + adminPubkey + `"],
 174  				"kind": {
 175  					"whitelist": [1, 3, 7]
 176  				},
 177  				"rules": {
 178  					"1": {
 179  						"description": "Text notes",
 180  						"write_allow": ["` + allowedPubkey + `"],
 181  						"size_limit": 20000
 182  					}
 183  				}
 184  			}`,
 185  			expectError: false,
 186  		},
 187  		{
 188  			name: "invalid - policy admin cannot modify owners",
 189  			newPolicy: `{
 190  				"default_policy": "allow",
 191  				"owners": ["dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"],
 192  				"policy_admins": ["` + adminPubkey + `"],
 193  				"kind": {
 194  					"whitelist": [1, 3, 7]
 195  				},
 196  				"rules": {
 197  					"1": {
 198  						"description": "Text notes",
 199  						"write_allow": ["` + allowedPubkey + `"],
 200  						"size_limit": 10000
 201  					}
 202  				}
 203  			}`,
 204  			expectError: true,
 205  			errorMsg:    "cannot modify the 'owners' field",
 206  		},
 207  		{
 208  			name: "invalid - policy admin cannot modify policy_admins",
 209  			newPolicy: `{
 210  				"default_policy": "allow",
 211  				"owners": ["` + ownerPubkey + `"],
 212  				"policy_admins": ["dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"],
 213  				"kind": {
 214  					"whitelist": [1, 3, 7]
 215  				},
 216  				"rules": {
 217  					"1": {
 218  						"description": "Text notes",
 219  						"write_allow": ["` + allowedPubkey + `"],
 220  						"size_limit": 10000
 221  					}
 222  				}
 223  			}`,
 224  			expectError: true,
 225  			errorMsg:    "cannot modify the 'policy_admins' field",
 226  		},
 227  		{
 228  			name: "invalid - policy admin cannot remove from kind whitelist",
 229  			newPolicy: `{
 230  				"default_policy": "allow",
 231  				"owners": ["` + ownerPubkey + `"],
 232  				"policy_admins": ["` + adminPubkey + `"],
 233  				"kind": {
 234  					"whitelist": [1, 3]
 235  				},
 236  				"rules": {
 237  					"1": {
 238  						"description": "Text notes",
 239  						"write_allow": ["` + allowedPubkey + `"],
 240  						"size_limit": 10000
 241  					}
 242  				}
 243  			}`,
 244  			expectError: true,
 245  			errorMsg:    "cannot remove kind 7 from whitelist",
 246  		},
 247  		{
 248  			name: "invalid - policy admin cannot remove from write_allow",
 249  			newPolicy: `{
 250  				"default_policy": "allow",
 251  				"owners": ["` + ownerPubkey + `"],
 252  				"policy_admins": ["` + adminPubkey + `"],
 253  				"kind": {
 254  					"whitelist": [1, 3, 7]
 255  				},
 256  				"rules": {
 257  					"1": {
 258  						"description": "Text notes",
 259  						"write_allow": [],
 260  						"size_limit": 10000
 261  					}
 262  				}
 263  			}`,
 264  			expectError: true,
 265  			errorMsg:    "cannot remove pubkey",
 266  		},
 267  		{
 268  			name: "invalid - policy admin cannot reduce size limit",
 269  			newPolicy: `{
 270  				"default_policy": "allow",
 271  				"owners": ["` + ownerPubkey + `"],
 272  				"policy_admins": ["` + adminPubkey + `"],
 273  				"kind": {
 274  					"whitelist": [1, 3, 7]
 275  				},
 276  				"rules": {
 277  					"1": {
 278  						"description": "Text notes",
 279  						"write_allow": ["` + allowedPubkey + `"],
 280  						"size_limit": 5000
 281  					}
 282  				}
 283  			}`,
 284  			expectError: true,
 285  			errorMsg:    "cannot reduce size_limit",
 286  		},
 287  		{
 288  			name: "invalid - policy admin cannot remove rule",
 289  			newPolicy: `{
 290  				"default_policy": "allow",
 291  				"owners": ["` + ownerPubkey + `"],
 292  				"policy_admins": ["` + adminPubkey + `"],
 293  				"kind": {
 294  					"whitelist": [1, 3, 7]
 295  				},
 296  				"rules": {}
 297  			}`,
 298  			expectError: true,
 299  			errorMsg:    "cannot remove rule for kind 1",
 300  		},
 301  		{
 302  			name: "valid - policy admin can add blacklist entries for non-admin users",
 303  			newPolicy: `{
 304  				"default_policy": "allow",
 305  				"owners": ["` + ownerPubkey + `"],
 306  				"policy_admins": ["` + adminPubkey + `"],
 307  				"kind": {
 308  					"whitelist": [1, 3, 7],
 309  					"blacklist": [4]
 310  				},
 311  				"rules": {
 312  					"1": {
 313  						"description": "Text notes",
 314  						"write_allow": ["` + allowedPubkey + `"],
 315  						"write_deny": ["eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"],
 316  						"size_limit": 10000
 317  					}
 318  				}
 319  			}`,
 320  			expectError: false,
 321  		},
 322  		{
 323  			name: "invalid - policy admin cannot blacklist owner in write_deny",
 324  			newPolicy: `{
 325  				"default_policy": "allow",
 326  				"owners": ["` + ownerPubkey + `"],
 327  				"policy_admins": ["` + adminPubkey + `"],
 328  				"kind": {
 329  					"whitelist": [1, 3, 7]
 330  				},
 331  				"rules": {
 332  					"1": {
 333  						"description": "Text notes",
 334  						"write_allow": ["` + allowedPubkey + `"],
 335  						"write_deny": ["` + ownerPubkey + `"],
 336  						"size_limit": 10000
 337  					}
 338  				}
 339  			}`,
 340  			expectError: true,
 341  			errorMsg:    "cannot blacklist owner",
 342  		},
 343  		{
 344  			name: "invalid - policy admin cannot blacklist other policy admin",
 345  			newPolicy: `{
 346  				"default_policy": "allow",
 347  				"owners": ["` + ownerPubkey + `"],
 348  				"policy_admins": ["` + adminPubkey + `"],
 349  				"kind": {
 350  					"whitelist": [1, 3, 7]
 351  				},
 352  				"rules": {
 353  					"1": {
 354  						"description": "Text notes",
 355  						"write_allow": ["` + allowedPubkey + `"],
 356  						"write_deny": ["` + adminPubkey + `"],
 357  						"size_limit": 10000
 358  					}
 359  				}
 360  			}`,
 361  			expectError: true,
 362  			errorMsg:    "cannot blacklist policy admin",
 363  		},
 364  		{
 365  			name: "valid - policy admin can blacklist whitelisted non-admin user",
 366  			newPolicy: `{
 367  				"default_policy": "allow",
 368  				"owners": ["` + ownerPubkey + `"],
 369  				"policy_admins": ["` + adminPubkey + `"],
 370  				"kind": {
 371  					"whitelist": [1, 3, 7]
 372  				},
 373  				"rules": {
 374  					"1": {
 375  						"description": "Text notes",
 376  						"write_allow": ["` + allowedPubkey + `"],
 377  						"write_deny": ["` + allowedPubkey + `"],
 378  						"size_limit": 10000
 379  					}
 380  				}
 381  			}`,
 382  			expectError: false,
 383  		},
 384  	}
 385  
 386  	for _, tt := range tests {
 387  		t.Run(tt.name, func(t *testing.T) {
 388  			err := basePolicy.ValidatePolicyAdminUpdate([]byte(tt.newPolicy), adminPubkeyBin)
 389  			if tt.expectError {
 390  				if err == nil {
 391  					t.Errorf("expected error containing %q, got nil", tt.errorMsg)
 392  				} else if tt.errorMsg != "" && !containsSubstring(err.Error(), tt.errorMsg) {
 393  					t.Errorf("expected error containing %q, got %q", tt.errorMsg, err.Error())
 394  				}
 395  			} else {
 396  				if err != nil {
 397  					t.Errorf("unexpected error: %v", err)
 398  				}
 399  			}
 400  		})
 401  	}
 402  }
 403  
 404  // TestIsOwner tests the IsOwner method
 405  func TestIsOwner(t *testing.T) {
 406  	ownerPubkey := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
 407  	nonOwnerPubkey := "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
 408  
 409  	_ = nonOwnerPubkey // Silence unused variable warning
 410  
 411  	policyJSON := `{
 412  		"default_policy": "allow",
 413  		"owners": ["` + ownerPubkey + `"]
 414  	}`
 415  
 416  	policy, err := New([]byte(policyJSON))
 417  	if err != nil {
 418  		t.Fatalf("failed to create policy: %v", err)
 419  	}
 420  
 421  	// Create binary pubkeys
 422  	ownerBin := make([]byte, 32)
 423  	for i := range ownerBin {
 424  		ownerBin[i] = 0xaa
 425  	}
 426  
 427  	nonOwnerBin := make([]byte, 32)
 428  	for i := range nonOwnerBin {
 429  		nonOwnerBin[i] = 0xbb
 430  	}
 431  
 432  	tests := []struct {
 433  		name     string
 434  		pubkey   []byte
 435  		expected bool
 436  	}{
 437  		{
 438  			name:     "owner is recognized",
 439  			pubkey:   ownerBin,
 440  			expected: true,
 441  		},
 442  		{
 443  			name:     "non-owner is not recognized",
 444  			pubkey:   nonOwnerBin,
 445  			expected: false,
 446  		},
 447  		{
 448  			name:     "nil pubkey returns false",
 449  			pubkey:   nil,
 450  			expected: false,
 451  		},
 452  		{
 453  			name:     "empty pubkey returns false",
 454  			pubkey:   []byte{},
 455  			expected: false,
 456  		},
 457  	}
 458  
 459  	for _, tt := range tests {
 460  		t.Run(tt.name, func(t *testing.T) {
 461  			result := policy.IsOwner(tt.pubkey)
 462  			if result != tt.expected {
 463  				t.Errorf("expected %v, got %v", tt.expected, result)
 464  			}
 465  		})
 466  	}
 467  }
 468  
 469  // TestStringSliceEqual tests the helper function
 470  func TestStringSliceEqual(t *testing.T) {
 471  	tests := []struct {
 472  		name     string
 473  		a        []string
 474  		b        []string
 475  		expected bool
 476  	}{
 477  		{
 478  			name:     "equal slices same order",
 479  			a:        []string{"a", "b", "c"},
 480  			b:        []string{"a", "b", "c"},
 481  			expected: true,
 482  		},
 483  		{
 484  			name:     "equal slices different order",
 485  			a:        []string{"a", "b", "c"},
 486  			b:        []string{"c", "a", "b"},
 487  			expected: true,
 488  		},
 489  		{
 490  			name:     "different lengths",
 491  			a:        []string{"a", "b"},
 492  			b:        []string{"a", "b", "c"},
 493  			expected: false,
 494  		},
 495  		{
 496  			name:     "different contents",
 497  			a:        []string{"a", "b", "c"},
 498  			b:        []string{"a", "b", "d"},
 499  			expected: false,
 500  		},
 501  		{
 502  			name:     "empty slices",
 503  			a:        []string{},
 504  			b:        []string{},
 505  			expected: true,
 506  		},
 507  		{
 508  			name:     "nil slices",
 509  			a:        nil,
 510  			b:        nil,
 511  			expected: true,
 512  		},
 513  		{
 514  			name:     "nil vs empty",
 515  			a:        nil,
 516  			b:        []string{},
 517  			expected: true,
 518  		},
 519  		{
 520  			name:     "duplicates in both",
 521  			a:        []string{"a", "a", "b"},
 522  			b:        []string{"a", "b", "a"},
 523  			expected: true,
 524  		},
 525  	}
 526  
 527  	for _, tt := range tests {
 528  		t.Run(tt.name, func(t *testing.T) {
 529  			result := stringSliceEqual(tt.a, tt.b)
 530  			if result != tt.expected {
 531  				t.Errorf("expected %v, got %v", tt.expected, result)
 532  			}
 533  		})
 534  	}
 535  }
 536  
 537  // TestPolicyAdminContributionValidation tests the contribution validation
 538  func TestPolicyAdminContributionValidation(t *testing.T) {
 539  	ownerPubkey := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
 540  	adminPubkey := "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
 541  
 542  	ownerPolicy := &P{
 543  		DefaultPolicy: "allow",
 544  		Owners:        []string{ownerPubkey},
 545  		PolicyAdmins:  []string{adminPubkey},
 546  		Kind: Kinds{
 547  			Whitelist: []int{1, 3, 7},
 548  		},
 549  		rules: map[int]Rule{
 550  			1: {
 551  				Description: "Text notes",
 552  				Constraints: Constraints{
 553  					SizeLimit: ptr(int64(10000)),
 554  				},
 555  			},
 556  		},
 557  	}
 558  
 559  	tests := []struct {
 560  		name         string
 561  		contribution *PolicyAdminContribution
 562  		expectError  bool
 563  		errorMsg     string
 564  	}{
 565  		{
 566  			name: "valid - add kinds to whitelist",
 567  			contribution: &PolicyAdminContribution{
 568  				AdminPubkey:      adminPubkey,
 569  				CreatedAt:        1234567890,
 570  				EventID:          "event123",
 571  				KindWhitelistAdd: []int{30023},
 572  			},
 573  			expectError: false,
 574  		},
 575  		{
 576  			name: "valid - add to blacklist",
 577  			contribution: &PolicyAdminContribution{
 578  				AdminPubkey:      adminPubkey,
 579  				CreatedAt:        1234567890,
 580  				EventID:          "event123",
 581  				KindBlacklistAdd: []int{4},
 582  			},
 583  			expectError: false,
 584  		},
 585  		{
 586  			name: "valid - extend existing rule with larger limit",
 587  			contribution: &PolicyAdminContribution{
 588  				AdminPubkey: adminPubkey,
 589  				CreatedAt:   1234567890,
 590  				EventID:     "event123",
 591  				RulesExtend: map[int]RuleExtension{
 592  					1: {
 593  						SizeLimitOverride: ptr(int64(20000)),
 594  					},
 595  				},
 596  			},
 597  			expectError: false,
 598  		},
 599  		{
 600  			name: "invalid - extend non-existent rule",
 601  			contribution: &PolicyAdminContribution{
 602  				AdminPubkey: adminPubkey,
 603  				CreatedAt:   1234567890,
 604  				EventID:     "event123",
 605  				RulesExtend: map[int]RuleExtension{
 606  					999: {
 607  						SizeLimitOverride: ptr(int64(20000)),
 608  					},
 609  				},
 610  			},
 611  			expectError: true,
 612  			errorMsg:    "cannot extend rule for kind 999",
 613  		},
 614  		{
 615  			name: "invalid - size limit override smaller than owner's",
 616  			contribution: &PolicyAdminContribution{
 617  				AdminPubkey: adminPubkey,
 618  				CreatedAt:   1234567890,
 619  				EventID:     "event123",
 620  				RulesExtend: map[int]RuleExtension{
 621  					1: {
 622  						SizeLimitOverride: ptr(int64(5000)),
 623  					},
 624  				},
 625  			},
 626  			expectError: true,
 627  			errorMsg:    "size_limit_override for kind 1 must be >=",
 628  		},
 629  		{
 630  			name: "valid - add new rule for undefined kind",
 631  			contribution: &PolicyAdminContribution{
 632  				AdminPubkey: adminPubkey,
 633  				CreatedAt:   1234567890,
 634  				EventID:     "event123",
 635  				RulesAdd: map[int]Rule{
 636  					30023: {
 637  						Description: "Long-form content",
 638  						Constraints: Constraints{
 639  							SizeLimit: ptr(int64(100000)),
 640  						},
 641  					},
 642  				},
 643  			},
 644  			expectError: false,
 645  		},
 646  		{
 647  			name: "invalid - add rule for already-defined kind",
 648  			contribution: &PolicyAdminContribution{
 649  				AdminPubkey: adminPubkey,
 650  				CreatedAt:   1234567890,
 651  				EventID:     "event123",
 652  				RulesAdd: map[int]Rule{
 653  					1: {
 654  						Description: "Trying to override",
 655  					},
 656  				},
 657  			},
 658  			expectError: true,
 659  			errorMsg:    "cannot add rule for kind 1: already defined",
 660  		},
 661  		{
 662  			name: "invalid - bad pubkey length in extension",
 663  			contribution: &PolicyAdminContribution{
 664  				AdminPubkey: "short",
 665  				CreatedAt:   1234567890,
 666  				EventID:     "event123",
 667  			},
 668  			expectError: true,
 669  			errorMsg:    "invalid admin pubkey length",
 670  		},
 671  	}
 672  
 673  	for _, tt := range tests {
 674  		t.Run(tt.name, func(t *testing.T) {
 675  			err := ValidatePolicyAdminContribution(ownerPolicy, tt.contribution, nil)
 676  			if tt.expectError {
 677  				if err == nil {
 678  					t.Errorf("expected error containing %q, got nil", tt.errorMsg)
 679  				} else if tt.errorMsg != "" && !containsSubstring(err.Error(), tt.errorMsg) {
 680  					t.Errorf("expected error containing %q, got %q", tt.errorMsg, err.Error())
 681  				}
 682  			} else {
 683  				if err != nil {
 684  					t.Errorf("unexpected error: %v", err)
 685  				}
 686  			}
 687  		})
 688  	}
 689  }
 690  
 691  // Helper function for generic pointer
 692  func ptr[T any](v T) *T {
 693  	return &v
 694  }
 695