package config

import (
	"encoding/json"
	"fmt"
	mathrand "math/rand/v2"
	"net/url"
	"strings"
	"testing"

	envoy_config_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
	"github.com/google/go-cmp/cmp"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"google.golang.org/protobuf/proto"

	"github.com/pomerium/pomerium/internal/urlutil"
	"github.com/pomerium/pomerium/pkg/cryptutil"
)

func Test_PolicyValidate(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name    string
		policy  Policy
		wantErr bool
	}{
		{"good", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld")}, false},
		{"empty to host", Policy{From: "https://httpbin.corp.example", To: []WeightedURL{{URL: url.URL{Scheme: "https", Path: "/"}}}}, true},
		{"empty from host", Policy{From: "https://", To: mustParseWeightedURLs(t, "https://httpbin.corp.example")}, true},
		{"empty from scheme", Policy{From: "httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.example")}, true},
		{"empty to scheme", Policy{From: "https://httpbin.corp.example", To: []WeightedURL{{URL: url.URL{Host: "httpbin.corp.example"}}}}, true},
		{"path in from", Policy{From: "https://httpbin.corp.example/some/path", To: mustParseWeightedURLs(t, "https://httpbin.corp.example")}, true},
		{"cors policy", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), CORSAllowPreflight: true}, false},
		{"public policy", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), AllowPublicUnauthenticatedAccess: true}, false},
		{"public and whitelist", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), AllowPublicUnauthenticatedAccess: true, AllowedUsers: []string{"test@domain.example"}}, true},
		{"route must have", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), AllowPublicUnauthenticatedAccess: true, AllowedUsers: []string{"test@domain.example"}}, true},
		{"any authenticated user policy", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), AllowAnyAuthenticatedUser: true}, false},
		{"any authenticated user and whitelist", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), AllowAnyAuthenticatedUser: true, AllowedUsers: []string{"test@domain.example"}}, true},
		{"good client cert", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), TLSClientKey: "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcGdJQkFBS0NBUUVBNjdLanFtUVlHcTBNVnRBQ1ZwZUNtWG1pbmxRYkRQR0xtc1pBVUV3dWVIUW5ydDNXCnR2cERPbTZBbGFKTVVuVytIdTU1ampva2FsS2VWalRLbWdZR2JxVXpWRG9NYlBEYUhla2x0ZEJUTUdsT1VGc1AKNFVKU0RyTzR6ZE4rem80MjhUWDJQbkcyRkNkVktHeTRQRThpbEhiV0xjcjg3MVlqVjUxZnc4Q0xEWDlQWkpOdQo4NjFDRjdWOWlFSm02c1NmUWxtbmhOOGozK1d6VmJQUU55MVdzUjdpOWU5ajYzRXFLdDIyUTlPWEwrV0FjS3NrCm9JU21DTlZSVUFqVThZUlZjZ1FKQit6UTM0QVFQbHowT3A1Ty9RTi9NZWRqYUY4d0xTK2l2L3p2aVM4Y3FQYngKbzZzTHE2Rk5UbHRrL1FreGVDZUtLVFFlLzNrUFl2UUFkbmw2NVFJREFRQUJBb0lCQVFEQVQ0eXN2V2pSY3pxcgpKcU9SeGFPQTJEY3dXazJML1JXOFhtQWhaRmRTWHV2MkNQbGxhTU1yelBmTG41WUlmaHQzSDNzODZnSEdZc3pnClo4aWJiYWtYNUdFQ0t5N3lRSDZuZ3hFS3pRVGpiampBNWR3S0h0UFhQUnJmamQ1Y2FMczVpcDcxaWxCWEYxU3IKWERIaXUycnFtaC9kVTArWGRMLzNmK2VnVDl6bFQ5YzRyUm84dnZueWNYejFyMnVhRVZ2VExsWHVsb2NpeEVrcgoySjlTMmxveWFUb2tFTnNlMDNpSVdaWnpNNElZcVowOGJOeG9IWCszQXVlWExIUStzRkRKMlhaVVdLSkZHMHUyClp3R2w3YlZpRTFQNXdiQUdtZzJDeDVCN1MrdGQyUEpSV3Frb2VxY3F2RVdCc3RFL1FEcDFpVThCOHpiQXd0Y3IKZHc5TXZ6Q2hBb0dCQVBObzRWMjF6MGp6MWdEb2tlTVN5d3JnL2E4RkJSM2R2Y0xZbWV5VXkybmd3eHVucnFsdwo2U2IrOWdrOGovcXEvc3VQSDhVdzNqSHNKYXdGSnNvTkVqNCt2b1ZSM3UrbE5sTEw5b21rMXBoU0dNdVp0b3huCm5nbUxVbkJUMGI1M3BURkJ5WGsveE5CbElreWdBNlg5T2MreW5na3RqNlRyVnMxUERTdnVJY0s1QW9HQkFQZmoKcEUzR2F6cVFSemx6TjRvTHZmQWJBdktCZ1lPaFNnemxsK0ZLZkhzYWJGNkdudFd1dWVhY1FIWFpYZTA1c2tLcApXN2xYQ3dqQU1iUXI3QmdlazcrOSszZElwL1RnYmZCYnN3Syt6Vng3Z2doeWMrdytXRWExaHByWTZ6YXdxdkFaCkhRU2lMUEd1UGp5WXBQa1E2ZFdEczNmWHJGZ1dlTmd4SkhTZkdaT05Bb0dCQUt5WTF3MUM2U3Y2c3VuTC8vNTcKQ2Z5NTAwaXlqNUZBOWRqZkRDNWt4K1JZMnlDV0ExVGsybjZyVmJ6dzg4czBTeDMrYS9IQW1CM2dMRXBSRU5NKwo5NHVwcENFWEQ3VHdlcGUxUnlrTStKbmp4TzlDSE41c2J2U25sUnBQWlMvZzJRTVhlZ3grK2trbkhXNG1ITkFyCndqMlRrMXBBczFXbkJ0TG9WaGVyY01jSkFvR0JBSTYwSGdJb0Y5SysvRUcyY21LbUg5SDV1dGlnZFU2eHEwK0IKWE0zMWMzUHE0amdJaDZlN3pvbFRxa2d0dWtTMjBraE45dC9ibkI2TmhnK1N1WGVwSXFWZldVUnlMejVwZE9ESgo2V1BMTTYzcDdCR3cwY3RPbU1NYi9VRm5Yd0U4OHlzRlNnOUF6VjdVVUQvU0lDYkI5ZHRVMWh4SHJJK0pZRWdWCkFrZWd6N2lCQW9HQkFJRncrQVFJZUIwM01UL0lCbGswNENQTDJEak0rNDhoVGRRdjgwMDBIQU9mUWJrMEVZUDEKQ2FLR3RDbTg2MXpBZjBzcS81REtZQ0l6OS9HUzNYRk00Qm1rRk9nY1NXVENPNmZmTGdLM3FmQzN4WDJudlpIOQpYZGNKTDQrZndhY0x4c2JJKzhhUWNOVHRtb3pkUjEzQnNmUmIrSGpUL2o3dkdrYlFnSkhCT0syegotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=", TLSClientCert: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVJVENDQWdtZ0F3SUJBZ0lSQVBqTEJxS1lwcWU0ekhQc0dWdFR6T0F3RFFZSktvWklodmNOQVFFTEJRQXcKRWpFUU1BNEdBMVVFQXhNSFoyOXZaQzFqWVRBZUZ3MHhPVEE0TVRBeE9EUTVOREJhRncweU1UQXlNVEF4TnpRdwpNREZhTUJNeEVUQVBCZ05WQkFNVENIQnZiV1Z5YVhWdE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBCk1JSUJDZ0tDQVFFQTY3S2pxbVFZR3EwTVZ0QUNWcGVDbVhtaW5sUWJEUEdMbXNaQVVFd3VlSFFucnQzV3R2cEQKT202QWxhSk1VblcrSHU1NWpqb2thbEtlVmpUS21nWUdicVV6VkRvTWJQRGFIZWtsdGRCVE1HbE9VRnNQNFVKUwpEck80emROK3pvNDI4VFgyUG5HMkZDZFZLR3k0UEU4aWxIYldMY3I4NzFZalY1MWZ3OENMRFg5UFpKTnU4NjFDCkY3VjlpRUptNnNTZlFsbW5oTjhqMytXelZiUFFOeTFXc1I3aTllOWo2M0VxS3QyMlE5T1hMK1dBY0tza29JU20KQ05WUlVBalU4WVJWY2dRSkIrelEzNEFRUGx6ME9wNU8vUU4vTWVkamFGOHdMUytpdi96dmlTOGNxUGJ4bzZzTApxNkZOVGx0ay9Ra3hlQ2VLS1RRZS8za1BZdlFBZG5sNjVRSURBUUFCbzNFd2J6QU9CZ05WSFE4QkFmOEVCQU1DCkE3Z3dIUVlEVlIwbEJCWXdGQVlJS3dZQkJRVUhBd0VHQ0NzR0FRVUZCd01DTUIwR0ExVWREZ1FXQkJRQ1FYbWIKc0hpcS9UQlZUZVhoQ0dpNjhrVy9DakFmQmdOVkhTTUVHREFXZ0JSNTRKQ3pMRlg0T0RTQ1J0dWNBUGZOdVhWegpuREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBZ0VBcm9XL2trMllleFN5NEhaQXFLNDVZaGQ5ay9QVTFiaDlFK1BRCk5jZFgzTUdEY2NDRUFkc1k4dll3NVE1cnhuMGFzcSt3VGFCcGxoYS9rMi9VVW9IQ1RqUVp1Mk94dEF3UTdPaWIKVE1tMEorU3NWT3d4YnFQTW9rK1RqVE16NFdXaFFUTzVwRmNoZDZXZXNCVHlJNzJ0aG1jcDd1c2NLU2h3YktIegpQY2h1QTQ4SzhPdi96WkxmZnduQVNZb3VCczJjd1ZiRDI3ZXZOMzdoMGFzR1BrR1VXdm1PSDduTHNVeTh3TTdqCkNGL3NwMmJmTC9OYVdNclJnTHZBMGZMS2pwWTQrVEpPbkVxQmxPcCsrbHlJTEZMcC9qMHNybjRNUnlKK0t6UTEKR1RPakVtQ1QvVEFtOS9XSThSL0FlYjcwTjEzTytYNEtaOUJHaDAxTzN3T1Vqd3BZZ3lxSnNoRnNRUG50VmMrSQpKQmF4M2VQU3NicUcwTFkzcHdHUkpRNmMrd1lxdGk2Y0tNTjliYlRkMDhCNUk1N1RRTHhNcUoycTFnWmw1R1VUCmVFZGNWRXltMnZmd0NPd0lrbGNBbThxTm5kZGZKV1FabE5VaHNOVWFBMkVINnlDeXdaZm9aak9hSDEwTXowV20KeTNpZ2NSZFQ3Mi9NR2VkZk93MlV0MVVvRFZmdEcxcysrditUQ1lpNmpUQU05dkZPckJ4UGlOeGFkUENHR2NZZAowakZIc2FWOGFPV1dQQjZBQ1JteHdDVDdRTnRTczM2MlpIOUlFWWR4Q00yMDUrZmluVHhkOUcwSmVRRTd2Kyt6CldoeWo2ZmJBWUIxM2wvN1hkRnpNSW5BOGxpekdrVHB2RHMxeTBCUzlwV3ppYmhqbVFoZGZIejdCZGpGTHVvc2wKZzlNZE5sND0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="}, false},
		{"bad base64 client cert", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), TLSClientKey: "!=", TLSClientCert: "!="}, true},
		{"bad one client cert empty", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), TLSClientKey: "", TLSClientCert: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVJVENDQWdtZ0F3SUJBZ0lSQVBqTEJxS1lwcWU0ekhQc0dWdFR6T0F3RFFZSktvWklodmNOQVFFTEJRQXcKRWpFUU1BNEdBMVVFQXhNSFoyOXZaQzFqWVRBZUZ3MHhPVEE0TVRBeE9EUTVOREJhRncweU1UQXlNVEF4TnpRdwpNREZhTUJNeEVUQVBCZ05WQkFNVENIQnZiV1Z5YVhWdE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBCk1JSUJDZ0tDQVFFQTY3S2pxbVFZR3EwTVZ0QUNWcGVDbVhtaW5sUWJEUEdMbXNaQVVFd3VlSFFucnQzV3R2cEQKT202QWxhSk1VblcrSHU1NWpqb2thbEtlVmpUS21nWUdicVV6VkRvTWJQRGFIZWtsdGRCVE1HbE9VRnNQNFVKUwpEck80emROK3pvNDI4VFgyUG5HMkZDZFZLR3k0UEU4aWxIYldMY3I4NzFZalY1MWZ3OENMRFg5UFpKTnU4NjFDCkY3VjlpRUptNnNTZlFsbW5oTjhqMytXelZiUFFOeTFXc1I3aTllOWo2M0VxS3QyMlE5T1hMK1dBY0tza29JU20KQ05WUlVBalU4WVJWY2dRSkIrelEzNEFRUGx6ME9wNU8vUU4vTWVkamFGOHdMUytpdi96dmlTOGNxUGJ4bzZzTApxNkZOVGx0ay9Ra3hlQ2VLS1RRZS8za1BZdlFBZG5sNjVRSURBUUFCbzNFd2J6QU9CZ05WSFE4QkFmOEVCQU1DCkE3Z3dIUVlEVlIwbEJCWXdGQVlJS3dZQkJRVUhBd0VHQ0NzR0FRVUZCd01DTUIwR0ExVWREZ1FXQkJRQ1FYbWIKc0hpcS9UQlZUZVhoQ0dpNjhrVy9DakFmQmdOVkhTTUVHREFXZ0JSNTRKQ3pMRlg0T0RTQ1J0dWNBUGZOdVhWegpuREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBZ0VBcm9XL2trMllleFN5NEhaQXFLNDVZaGQ5ay9QVTFiaDlFK1BRCk5jZFgzTUdEY2NDRUFkc1k4dll3NVE1cnhuMGFzcSt3VGFCcGxoYS9rMi9VVW9IQ1RqUVp1Mk94dEF3UTdPaWIKVE1tMEorU3NWT3d4YnFQTW9rK1RqVE16NFdXaFFUTzVwRmNoZDZXZXNCVHlJNzJ0aG1jcDd1c2NLU2h3YktIegpQY2h1QTQ4SzhPdi96WkxmZnduQVNZb3VCczJjd1ZiRDI3ZXZOMzdoMGFzR1BrR1VXdm1PSDduTHNVeTh3TTdqCkNGL3NwMmJmTC9OYVdNclJnTHZBMGZMS2pwWTQrVEpPbkVxQmxPcCsrbHlJTEZMcC9qMHNybjRNUnlKK0t6UTEKR1RPakVtQ1QvVEFtOS9XSThSL0FlYjcwTjEzTytYNEtaOUJHaDAxTzN3T1Vqd3BZZ3lxSnNoRnNRUG50VmMrSQpKQmF4M2VQU3NicUcwTFkzcHdHUkpRNmMrd1lxdGk2Y0tNTjliYlRkMDhCNUk1N1RRTHhNcUoycTFnWmw1R1VUCmVFZGNWRXltMnZmd0NPd0lrbGNBbThxTm5kZGZKV1FabE5VaHNOVWFBMkVINnlDeXdaZm9aak9hSDEwTXowV20KeTNpZ2NSZFQ3Mi9NR2VkZk93MlV0MVVvRFZmdEcxcysrditUQ1lpNmpUQU05dkZPckJ4UGlOeGFkUENHR2NZZAowakZIc2FWOGFPV1dQQjZBQ1JteHdDVDdRTnRTczM2MlpIOUlFWWR4Q00yMDUrZmluVHhkOUcwSmVRRTd2Kyt6CldoeWo2ZmJBWUIxM2wvN1hkRnpNSW5BOGxpekdrVHB2RHMxeTBCUzlwV3ppYmhqbVFoZGZIejdCZGpGTHVvc2wKZzlNZE5sND0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="}, true},
		{"bad th other client cert empty", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), TLSClientKey: "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcGdJQkFBS0NBUUVBNjdLanFtUVlHcTBNVnRBQ1ZwZUNtWG1pbmxRYkRQR0xtc1pBVUV3dWVIUW5ydDNXCnR2cERPbTZBbGFKTVVuVytIdTU1ampva2FsS2VWalRLbWdZR2JxVXpWRG9NYlBEYUhla2x0ZEJUTUdsT1VGc1AKNFVKU0RyTzR6ZE4rem80MjhUWDJQbkcyRkNkVktHeTRQRThpbEhiV0xjcjg3MVlqVjUxZnc4Q0xEWDlQWkpOdQo4NjFDRjdWOWlFSm02c1NmUWxtbmhOOGozK1d6VmJQUU55MVdzUjdpOWU5ajYzRXFLdDIyUTlPWEwrV0FjS3NrCm9JU21DTlZSVUFqVThZUlZjZ1FKQit6UTM0QVFQbHowT3A1Ty9RTi9NZWRqYUY4d0xTK2l2L3p2aVM4Y3FQYngKbzZzTHE2Rk5UbHRrL1FreGVDZUtLVFFlLzNrUFl2UUFkbmw2NVFJREFRQUJBb0lCQVFEQVQ0eXN2V2pSY3pxcgpKcU9SeGFPQTJEY3dXazJML1JXOFhtQWhaRmRTWHV2MkNQbGxhTU1yelBmTG41WUlmaHQzSDNzODZnSEdZc3pnClo4aWJiYWtYNUdFQ0t5N3lRSDZuZ3hFS3pRVGpiampBNWR3S0h0UFhQUnJmamQ1Y2FMczVpcDcxaWxCWEYxU3IKWERIaXUycnFtaC9kVTArWGRMLzNmK2VnVDl6bFQ5YzRyUm84dnZueWNYejFyMnVhRVZ2VExsWHVsb2NpeEVrcgoySjlTMmxveWFUb2tFTnNlMDNpSVdaWnpNNElZcVowOGJOeG9IWCszQXVlWExIUStzRkRKMlhaVVdLSkZHMHUyClp3R2w3YlZpRTFQNXdiQUdtZzJDeDVCN1MrdGQyUEpSV3Frb2VxY3F2RVdCc3RFL1FEcDFpVThCOHpiQXd0Y3IKZHc5TXZ6Q2hBb0dCQVBObzRWMjF6MGp6MWdEb2tlTVN5d3JnL2E4RkJSM2R2Y0xZbWV5VXkybmd3eHVucnFsdwo2U2IrOWdrOGovcXEvc3VQSDhVdzNqSHNKYXdGSnNvTkVqNCt2b1ZSM3UrbE5sTEw5b21rMXBoU0dNdVp0b3huCm5nbUxVbkJUMGI1M3BURkJ5WGsveE5CbElreWdBNlg5T2MreW5na3RqNlRyVnMxUERTdnVJY0s1QW9HQkFQZmoKcEUzR2F6cVFSemx6TjRvTHZmQWJBdktCZ1lPaFNnemxsK0ZLZkhzYWJGNkdudFd1dWVhY1FIWFpYZTA1c2tLcApXN2xYQ3dqQU1iUXI3QmdlazcrOSszZElwL1RnYmZCYnN3Syt6Vng3Z2doeWMrdytXRWExaHByWTZ6YXdxdkFaCkhRU2lMUEd1UGp5WXBQa1E2ZFdEczNmWHJGZ1dlTmd4SkhTZkdaT05Bb0dCQUt5WTF3MUM2U3Y2c3VuTC8vNTcKQ2Z5NTAwaXlqNUZBOWRqZkRDNWt4K1JZMnlDV0ExVGsybjZyVmJ6dzg4czBTeDMrYS9IQW1CM2dMRXBSRU5NKwo5NHVwcENFWEQ3VHdlcGUxUnlrTStKbmp4TzlDSE41c2J2U25sUnBQWlMvZzJRTVhlZ3grK2trbkhXNG1ITkFyCndqMlRrMXBBczFXbkJ0TG9WaGVyY01jSkFvR0JBSTYwSGdJb0Y5SysvRUcyY21LbUg5SDV1dGlnZFU2eHEwK0IKWE0zMWMzUHE0amdJaDZlN3pvbFRxa2d0dWtTMjBraE45dC9ibkI2TmhnK1N1WGVwSXFWZldVUnlMejVwZE9ESgo2V1BMTTYzcDdCR3cwY3RPbU1NYi9VRm5Yd0U4OHlzRlNnOUF6VjdVVUQvU0lDYkI5ZHRVMWh4SHJJK0pZRWdWCkFrZWd6N2lCQW9HQkFJRncrQVFJZUIwM01UL0lCbGswNENQTDJEak0rNDhoVGRRdjgwMDBIQU9mUWJrMEVZUDEKQ2FLR3RDbTg2MXpBZjBzcS81REtZQ0l6OS9HUzNYRk00Qm1rRk9nY1NXVENPNmZmTGdLM3FmQzN4WDJudlpIOQpYZGNKTDQrZndhY0x4c2JJKzhhUWNOVHRtb3pkUjEzQnNmUmIrSGpUL2o3dkdrYlFnSkhCT0syegotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=", TLSClientCert: ""}, true},
		{"good root ca pool", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), TLSCustomCA: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUU1RENDQXN5Z0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBREFTTVJBd0RnWURWUVFERXdkbmIyOWsKTFdOaE1CNFhEVEU1TURneE1ERTNOREF3TWxvWERUSXhNREl4TURFM05EQXdNbG93RWpFUU1BNEdBMVVFQXhNSApaMjl2WkMxallUQ0NBaUl3RFFZSktvWklodmNOQVFFQkJRQURnZ0lQQURDQ0Fnb0NnZ0lCQUw3b2VldEovNmNFCkdicTcvanNtcU9FM2VyVE1aRHR0eFM4STVGV1c0TkRXbWNpOE5IdWRMZDhlM1JtOEh6Y09jSjRQL0ErcDVsYmsKTjhySzY4OUlsQzhqM28yaEhSdEk2T21saFY3NEoxaUlIOGtkSXU2V2xPMWtOdUx5dGRrbjhRaytJOUNEWjlGSAorZzhRbnVka0tMUWJkZFdDVXJzUjR4cEcyK0VkNWdua0JJNG4zbmNLMFgvWEZocWhDTEU1eFBaQk5OWktGbHJxCm1lYUl4dHoyc2ZvWVY1NmcwMnNGS1QxSUlMNTVFMG14djRUa2JtSWw5Rk9qZEtCdkhFZnJHeXl5OFRGTHErUzMKTXo2em9xNDhuOEhGMUc5cHBLVk9OMUp0Mks1UWEvV2hpbjVrcWNhYTNwNE0vN2tiNmtxU0tMWG1iN0gyN3kvVQpEYjZDUG01d2lodjA2c1FobXN2MHhuS2hqMm8vQzhlcWxzNzZZWDF1Y2NqMzlmSTRlQ1E4cENFbTlVcDh5ZkkvCkxlYVpXbGE0NEZneWw3N1lyc2MvM0U5dk1hS0ZVeGRjR3VtMXQrNUZZYWpkY0EvTlFreTJBeTJqcHRwVXV1SFUKNnhYSzdEcXY5Z01jQS8zM1VYOFpHZklPRk0rY3FlOTQxaTVPT1hGSHJoRDlqeTRQR2M4Z2kxSTRyK1VXd0tCYgoxSGg1clQ3ckJZK1NLTTBzZmtpQlZ1RU9pbnk2dDF1Z2tEdjY4dXNFWFlIWlZXaWl6b1hmcDVHbjZmckUvd1IxCkRkak13TGEvT2tQTnVEVVQ4eU1GS2hWRnFHcXdHQzY2bys1cjQyMlVwa0s4SHJ5K2tsQ3pUTys3U0RodTJiWk4KUVFGT0NLSVVldnR3bGdabVBNck1BNTZ3dzVSSnNhVnhBZ01CQUFHalJUQkRNQTRHQTFVZER3RUIvd1FFQXdJQgpCakFTQmdOVkhSTUJBZjhFQ0RBR0FRSC9BZ0VBTUIwR0ExVWREZ1FXQkJSNTRKQ3pMRlg0T0RTQ1J0dWNBUGZOCnVYVnpuREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBZ0VBZituUmpBVnZuT0pSckpBQWpKWVY3aVF3bHExUXZYRGcKbHZhY0JoVFJyWFh4OW5GaVRZUzV4MkFMbXZ5WHhubTdIS2VDSUZEclJwOE5MVFkyYjJXR01BcTFxc3JBT0QvegpTNmNSSW1OQ21QNmd0UHNUNDlabzBYajNrZjZyTXBPeHBiSUlnSmZMY056UGZpL25jeC9oRDNBOHl6Zk4wQTZZCnFFd2QvSkZPajdEa3RaQmdlSXZETlJXS0pveEpJRlZ4anJqLzFiVmkxZTRWVjVvWmhOako4SzlyV1FRK1EvK3QKZ3lGK0sycGxDQ1RiRWR6eU9heDY1djh5UDJ5RCs2WkFIRk9sRjI2TnZpUkw4OWJ1VHIwaEpZa0N5VXZ3MmJZaQo4Q3MyWDZkd0NDdXVhZUdVR2VRemszMGxQeUdWSmVKL3ZJMGJRSzlpZ2I5dFozY3d0WHBQdjN6a1B1TDE3d01WCitCMXo2RW1HZVVLNXlTQ0xFWjc2aVliNU0vY3ZjTUVOMWdoeFNIN0FmaDhMS0c0eWszT21SQ253akVqdTFhaWoKZGs3cjJuc0xmYU9KWFBRNU1wMzRYU1ltdTlpTVl0VytMbWZiSDJxMW9vS3dKZDhHNVhhRWRmQmpHUEQ5Q3FkWAphSlh0MDA0cVdsalJOS3p1MFNFRmJ6UldGNHRoeXlUTzE4QVI4eTNHV0Vwak95amdKSzlFeU1sQm9Qa3RYQVVVCjZzTFhqT3ZZU0ovd202NUhxVVZBTTVsRy96WVN3TGdCTDAwc1pJKzVGa0QwblU0Rkx6QWRLV05LWkRXZFVNbUwKVi9lV0ZGNGwwVFBvNTVhM0pUL1BGc2J0RFBLVWxvWVFXeTFybmFqR3J1L0Y5bGRCcHB1bUVUa2FOS2ZWT05Jcgp4cERnc1FhVkVXOD0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="}, false},
		{"bad root ca pool", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), TLSCustomCA: "!"}, true},
		{"good custom ca file", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), TLSCustomCAFile: "testdata/ca.pem"}, false},
		{"bad custom ca file", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), TLSCustomCAFile: "testdata/404.pem"}, true},
		{"good client certificate files", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), TLSClientCertFile: "testdata/example-cert.pem", TLSClientKeyFile: "testdata/example-key.pem"}, false},
		{"bad certificate file", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), TLSClientCertFile: "testdata/example-cert-404.pem", TLSClientKeyFile: "testdata/example-key.pem"}, true},
		{"bad key file", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://httpbin.corp.notatld"), TLSClientCertFile: "testdata/example-cert.pem", TLSClientKeyFile: "testdata/example-key-404.pem"}, true},
		{"good tls server name", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://internal-host-name"), TLSServerName: "httpbin.corp.notatld"}, false},
		{"good kube service account token file", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://internal-host-name"), KubernetesServiceAccountTokenFile: "testdata/kubeserviceaccount.token"}, false},
		{"good kube service account token", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://internal-host-name"), KubernetesServiceAccountToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE1OTY1MDk4MjIsImV4cCI6MTYyODA0NTgyMiwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.H0I6ccQrL6sKobsKQj9dqNcLw_INhU9_xJsVyCkgkiY"}, false},
		{"bad kube service account token and file", Policy{From: "https://httpbin.corp.example", To: mustParseWeightedURLs(t, "https://internal-host-name"), KubernetesServiceAccountToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE1OTY1MDk4MjIsImV4cCI6MTYyODA0NTgyMiwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.H0I6ccQrL6sKobsKQj9dqNcLw_INhU9_xJsVyCkgkiY", KubernetesServiceAccountTokenFile: "testdata/kubeserviceaccount.token"}, true},
		{"TCP To URLs", Policy{From: "tcp+https://httpbin.corp.example:4000", To: mustParseWeightedURLs(t, "tcp://one.example.com:5000", "tcp://two.example.com:5000")}, false},
		{"mix of TCP and non-TCP To URLs", Policy{From: "tcp+https://httpbin.corp.example:4000", To: mustParseWeightedURLs(t, "https://example.com", "tcp://example.com:5000")}, true},
		{"UDP To URLs", Policy{From: "udp+https://httpbin.corp.example:4000", To: mustParseWeightedURLs(t, "udp://one.example.com:5000", "udp://two.example.com:5000")}, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := tt.policy.Validate()
			if (err != nil) != tt.wantErr {
				t.Errorf("Validate() error = %v, want %v", err, tt.wantErr)
			}
		})
	}
}

func Test_PolicyValidate_RedirectResponseCode(t *testing.T) {
	t.Parallel()

	var r PolicyRedirect
	p := Policy{
		From:     "http://example.com",
		Redirect: &r,
	}

	cases := []struct {
		Code          *int32
		ExpectedError string
	}{
		{nil, ""},
		{proto.Int32(0), "unsupported redirect response code 0"},
		{proto.Int32(100), "unsupported redirect response code 100"},
		{proto.Int32(200), "unsupported redirect response code 200"},
		{proto.Int32(300), "unsupported redirect response code 300"},
		{proto.Int32(301), ""},
		{proto.Int32(302), ""},
		{proto.Int32(303), ""},
		{proto.Int32(304), "unsupported redirect response code 304"},
		{proto.Int32(305), "unsupported redirect response code 305"},
		{proto.Int32(306), "unsupported redirect response code 306"},
		{proto.Int32(307), ""},
		{proto.Int32(308), ""},
		{proto.Int32(309), "unsupported redirect response code 309"},
		{proto.Int32(400), "unsupported redirect response code 400"},
		{proto.Int32(500), "unsupported redirect response code 500"},
		{proto.Int32(600), "unsupported redirect response code 600"},
	}

	for i := range cases {
		c := &cases[i]
		t.Run(fmt.Sprint(c.Code), func(t *testing.T) {
			r.ResponseCode = c.Code
			err := p.Validate()
			if c.ExpectedError != "" {
				assert.ErrorContains(t, err, c.ExpectedError)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestPolicy_String(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name     string
		From     string
		To       []WeightedURL
		want     string
		wantFrom string
	}{
		{"good", "https://pomerium.io", []WeightedURL{{URL: url.URL{Scheme: "https", Host: "localhost"}}}, "https://pomerium.io → https://localhost", `"https://pomerium.io"`},
		{"invalid", "https://pomerium.io", nil, "https://pomerium.io → ?", `"https://pomerium.io"`},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			p := &Policy{
				From: tt.From,
				To:   tt.To,
			}
			p.Validate()
			if got := p.String(); got != tt.want {
				t.Errorf("Policy.String() = %v, want %v", got, tt.want)
			}
			out, err := json.Marshal(p.From)
			if err != nil {
				t.Fatal(err)
			}
			if diff := cmp.Diff(string(out), tt.wantFrom); diff != "" {
				t.Errorf("json diff() = %s", diff)
			}
		})
	}
}

func Test_PolicyRouteID(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name          string
		basePolicy    *Policy
		comparePolicy *Policy
		wantSame      bool
	}{
		{
			"same",
			&Policy{From: "https://pomerium.io", To: mustParseWeightedURLs(t, "http://localhost"), AllowedUsers: []string{"foo@bar.com"}},
			&Policy{From: "https://pomerium.io", To: mustParseWeightedURLs(t, "http://localhost")},
			true,
		},
		{
			"different from",
			&Policy{From: "https://pomerium.io", To: mustParseWeightedURLs(t, "http://localhost")},
			&Policy{From: "https://notpomerium.io", To: mustParseWeightedURLs(t, "http://localhost")},
			false,
		},
		{
			"different path",
			&Policy{From: "https://pomerium.io", To: mustParseWeightedURLs(t, "http://localhost")},
			&Policy{From: "https://pomerium.io", To: mustParseWeightedURLs(t, "http://localhost"), Path: "/foo"},
			false,
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			assert.NoError(t, tt.basePolicy.Validate())
			assert.NoError(t, tt.comparePolicy.Validate())

			id1, err := tt.basePolicy.RouteID()
			assert.NoError(t, err)

			id2, err := tt.comparePolicy.RouteID()
			assert.NoError(t, err)

			assert.Equal(t, tt.wantSame, id1 == id2)
		})
	}
}

func TestPolicy_Checksum(t *testing.T) {
	t.Parallel()
	p := &Policy{From: "https://pomerium.io", To: mustParseWeightedURLs(t, "http://localhost"), AllowedUsers: []string{"foo@bar.com"}}
	oldChecksum := p.Checksum()
	p.AllowedUsers = []string{"foo@pomerium.io"}
	newChecksum := p.Checksum()

	if newChecksum == oldChecksum {
		t.Errorf("Checksum() failed to update old = %d, new = %d", oldChecksum, newChecksum)
	}

	if newChecksum == 0 || oldChecksum == 0 {
		t.Error("Checksum() not returning data")
	}

	if p.Checksum() != newChecksum {
		t.Error("Checksum() inconsistent")
	}
}

func TestPolicy_FromToPb(t *testing.T) {
	t.Parallel()

	t.Run("normal", func(t *testing.T) {
		p := &Policy{
			Name:         "ROUTE_NAME",
			Description:  "DESCRIPTION",
			LogoURL:      "LOGO_URL",
			From:         "https://pomerium.io",
			To:           mustParseWeightedURLs(t, "http://localhost"),
			AllowedUsers: []string{"foo@bar.com"},
			SubPolicies: []SubPolicy{
				{
					ID:   "sub_policy_id",
					Name: "sub_policy",
					Rego: []string{"deny = true"},
				},
			},
			EnableGoogleCloudServerlessAuthentication: true,
		}
		pbPolicy, err := p.ToProto()
		require.NoError(t, err)

		policyFromPb, err := NewPolicyFromProto(pbPolicy)
		assert.NoError(t, err)
		assert.Equal(t, p.Name, policyFromPb.Name)
		assert.Equal(t, p.Description, policyFromPb.Description)
		assert.Equal(t, p.LogoURL, policyFromPb.LogoURL)
		assert.Equal(t, p.From, policyFromPb.From)
		assert.Equal(t, p.To, policyFromPb.To)
		assert.Equal(t, p.AllowedUsers, policyFromPb.AllowedUsers)
	})

	t.Run("envoy cluster name", func(t *testing.T) {
		p := &Policy{
			From:         "https://pomerium.io",
			To:           mustParseWeightedURLs(t, "http://localhost"),
			AllowedUsers: []string{"foo@bar.com"},
		}

		pbPolicy, err := p.ToProto()
		require.NoError(t, err)

		cases := []struct {
			pbPolicyName       string
			pbEnvoyOpts        *envoy_config_cluster_v3.Cluster
			expectedPolicyName string
		}{
			{"", nil, ""},
			{"pb-name", nil, "pb-name"},
			{"", &envoy_config_cluster_v3.Cluster{Name: "pb-envoy-name"}, "pb-envoy-name"},
			{"pb-name", &envoy_config_cluster_v3.Cluster{Name: "pb-envoy-name"}, "pb-envoy-name"},
		}

		for _, tc := range cases {
			pbPolicy.Name = tc.pbPolicyName
			pbPolicy.EnvoyOpts = tc.pbEnvoyOpts

			policyFromPb, err := NewPolicyFromProto(pbPolicy)
			assert.NoError(t, err)
			assert.Equal(t, tc.expectedPolicyName, policyFromPb.EnvoyOpts.Name)
		}
	})

	t.Run("redirect route", func(t *testing.T) {
		p := &Policy{
			From: "https://pomerium.io",
			Redirect: &PolicyRedirect{
				HTTPSRedirect: proto.Bool(true),
			},
		}

		pbPolicy, err := p.ToProto()
		require.NoError(t, err)

		policyFromProto, err := NewPolicyFromProto(pbPolicy)
		assert.NoError(t, err)
		assert.Equal(t, p.Redirect.HTTPSRedirect, policyFromProto.Redirect.HTTPSRedirect)
	})
}

func TestPolicy_Matches(t *testing.T) {
	t.Run("full", func(t *testing.T) {
		p := &Policy{
			From:  "https://www.example.com",
			To:    mustParseWeightedURLs(t, "https://localhost"),
			Regex: `/foo`,
		}
		assert.NoError(t, p.Validate())

		assert.False(t, p.Matches(urlutil.MustParseAndValidateURL(`https://www.example.com/foo/bar`), true),
			"regex should only match full string")
	})
	t.Run("issue2952", func(t *testing.T) {
		p := &Policy{
			From:  "https://www.example.com",
			To:    mustParseWeightedURLs(t, "https://localhost"),
			Regex: `^\/foo\/bar\/[0-9a-f]\/{0,1}$`,
		}
		assert.NoError(t, p.Validate())

		assert.True(t, p.Matches(urlutil.MustParseAndValidateURL(`https://www.example.com/foo/bar/0`), true))
	})
	t.Run("issue2592-test2", func(t *testing.T) {
		p := &Policy{
			From:  "https://www.example.com",
			To:    mustParseWeightedURLs(t, "https://localhost"),
			Regex: `/admin/.*`,
		}
		assert.NoError(t, p.Validate())

		assert.True(t, p.Matches(urlutil.MustParseAndValidateURL(`https://www.example.com/admin/foo`), true))
		assert.True(t, p.Matches(urlutil.MustParseAndValidateURL(`https://www.example.com/admin/bar`), true))
	})
	t.Run("tcp", func(t *testing.T) {
		p := &Policy{
			From: "tcp+https://proxy.example.com/tcp.example.com:6379",
			To:   mustParseWeightedURLs(t, "tcp://localhost:6379"),
		}
		assert.NoError(t, p.Validate())

		assert.True(t, p.Matches(urlutil.MustParseAndValidateURL(`https://tcp.example.com:6379`), true))
	})
}

func TestPolicy_SortOrder(t *testing.T) {
	ptr := func(i int64) *int64 {
		return &i
	}

	testCases := []struct {
		name     string
		policies []Policy
		wantIDs  []string
	}{
		{
			name: "regexPriorityOrder DESC NULLS LAST",
			policies: []Policy{
				{From: "a", Path: "/a", RegexPriorityOrder: nil, ID: "3"},
				{From: "a", Path: "/a", RegexPriorityOrder: ptr(2), ID: "2"},
				{From: "a", Path: "/a", RegexPriorityOrder: ptr(1), ID: "1"},
			},
			wantIDs: []string{"2", "1", "3"},
		},
		{
			name: "from ASC",
			policies: []Policy{
				{From: "", Path: "", RegexPriorityOrder: nil, ID: "B"},
				{From: "", Path: "", RegexPriorityOrder: ptr(0), ID: "C"},
				{From: "source", Path: "/a", RegexPriorityOrder: ptr(1), ID: "A"},
			},
			wantIDs: []string{"C", "B", "A"},
		},
		{
			name: "id ASC",
			policies: []Policy{
				{From: "source", Path: "/a", RegexPriorityOrder: ptr(1), Regex: "regex", Prefix: "prefix", ID: "2"},
				{From: "source", Path: "/a", RegexPriorityOrder: ptr(1), Regex: "regex", Prefix: "prefix", ID: "1"},
			},
			wantIDs: []string{"1", "2"},
		},
		{
			name: "path DESC",
			policies: []Policy{
				{From: "source", Path: "/b", RegexPriorityOrder: ptr(1), ID: "3"},
				{From: "source", Path: "/a", RegexPriorityOrder: nil, ID: "2"},
				{From: "source", Path: "/a", RegexPriorityOrder: ptr(2), ID: "1"},
			},
			wantIDs: []string{"3", "1", "2"},
		},
	}

	for _, tt := range testCases {
		t.Run(tt.name, func(t *testing.T) {
			SortPolicies(tt.policies)

			gotIDs := make([]string, 0, len(tt.policies))
			for _, entity := range tt.policies {
				gotIDs = append(gotIDs, entity.ID)
			}

			assert.Equal(t, tt.wantIDs, gotIDs)
		})
	}
}

func TestPolicy_IsTCP(t *testing.T) {
	p1 := Policy{From: "https://example.com"}
	assert.False(t, p1.IsTCP())

	p2 := Policy{From: "tcp+https://example.com"}
	assert.True(t, p2.IsTCP())
}

func TestPolicy_IsTCPUpstream(t *testing.T) {
	p1 := Policy{
		From: "tcp+https://example.com:1234",
		To:   mustParseWeightedURLs(t, "https://one.example.com", "https://two.example.com"),
	}
	assert.False(t, p1.IsTCPUpstream())

	p2 := Policy{
		From: "tcp+https://example.com:1234",
		To:   mustParseWeightedURLs(t, "tcp://one.example.com:4000", "tcp://two.example.com:4000"),
	}
	assert.True(t, p2.IsTCPUpstream())

	p3 := Policy{
		From: "tcp+https://example.com:1234",
	}
	assert.False(t, p3.IsTCPUpstream())
}

func mustParseWeightedURLs(t testing.TB, urls ...string) []WeightedURL {
	wu, err := ParseWeightedUrls(urls...)
	require.NoError(t, err)
	return wu
}

func TestRouteID(t *testing.T) {
	randomString := func() string {
		return strings.TrimSuffix(cryptutil.NewRandomStringN(mathrand.IntN(31)+1), "=")
	}
	randomBool := func() bool {
		return mathrand.N(2) == 0
	}
	randomURL := func() *url.URL {
		u, err := url.Parse(fmt.Sprintf("https://%s.example.com/%s?foo=%s#%s",
			randomString(), randomString(), randomString(), randomString()))
		require.NoError(t, err)
		return u
	}
	baseFieldMutators := []func(p *Policy){
		func(p *Policy) { p.From = randomString() },
		func(p *Policy) { p.Prefix = randomString() },
		func(p *Policy) { p.Path = randomString() },
		func(p *Policy) { p.Regex = randomString() },
	}
	toMutators := func(p *Policy) {
		p.To = make(WeightedURLs, mathrand.N(9)+1)
		for i := 0; i < len(p.To); i++ {
			p.To[i] = WeightedURL{URL: *randomURL(), LbWeight: mathrand.Uint32()}
		}
	}
	redirectMutators := []func(p *PolicyRedirect){
		func(p *PolicyRedirect) { p.HTTPSRedirect = randomPtr(10, randomBool()) },
		func(p *PolicyRedirect) { p.SchemeRedirect = randomPtr(10, randomString()) },
		func(p *PolicyRedirect) { p.HostRedirect = randomPtr(10, randomString()) },
		func(p *PolicyRedirect) { p.PortRedirect = randomPtr(10, mathrand.Uint32()) },
		func(p *PolicyRedirect) { p.PathRedirect = randomPtr(10, randomString()) },
		func(p *PolicyRedirect) { p.PrefixRewrite = randomPtr(10, randomString()) },
		func(p *PolicyRedirect) { p.ResponseCode = randomPtr(10, mathrand.Int32()) },
		func(p *PolicyRedirect) { p.StripQuery = randomPtr(10, randomBool()) },
	}
	responseMutators := []func(p *DirectResponse){
		func(p *DirectResponse) { p.Status = mathrand.Int() },
		func(p *DirectResponse) { p.Body = randomString() },
	}

	t.Run("random policies", func(t *testing.T) {
		hashes := make(map[uint64]struct{}, 10000)
		for i := 0; i < 10000; i++ {
			p := Policy{}
			for _, m := range baseFieldMutators {
				m(&p)
			}
			switch mathrand.IntN(3) {
			case 0:
				toMutators(&p)
			case 1:
				p.Redirect = &PolicyRedirect{}
				for _, m := range redirectMutators {
					m(p.Redirect)
				}
			case 2:
				p.Response = &DirectResponse{}
				for _, m := range responseMutators {
					m(p.Response)
				}
			}

			routeID, err := p.RouteID()
			require.NoError(t, err)
			hashes[routeID] = struct{}{} // odds of a collision should be pretty low here

			// check that computing the route id again results in the same value
			routeID2, err := p.RouteID()
			require.NoError(t, err)
			assert.Equal(t, routeID, routeID2)
		}
		assert.Len(t, hashes, 10000)
	})
	t.Run("incremental policy", func(t *testing.T) {
		hashes := make(map[uint64]Policy, 5000)

		p := Policy{}

		checkAdd := func(p *Policy) {
			routeID, err := p.RouteID()
			require.NoError(t, err)
			if existing, ok := hashes[routeID]; ok {
				require.Equal(t, existing, *p)
			} else {
				hashes[routeID] = *p
			}

			// check that computing the route id again results in the same value
			routeID2, err := p.RouteID()
			require.NoError(t, err)
			assert.Equal(t, routeID, routeID2)
		}

		// to
		toMutators(&p)
		checkAdd(&p)

		// set base fields
		for _, m := range baseFieldMutators {
			m(&p)
			checkAdd(&p)
		}

		// redirect
		p.To = nil
		p.Redirect = &PolicyRedirect{}
		for range 1000 {
			for _, m := range redirectMutators {
				m(p.Redirect)
				checkAdd(&p)
			}
		}

		// update base fields
		for _, m := range baseFieldMutators {
			m(&p)
			checkAdd(&p)
		}

		// direct response
		p.Redirect = nil
		p.Response = &DirectResponse{}
		for range 1000 {
			for _, m := range responseMutators {
				m(p.Response)
				checkAdd(&p)
			}
		}

		// update base fields
		for _, m := range baseFieldMutators {
			m(&p)
			checkAdd(&p)
		}

		// sanity check
		assert.Greater(t, len(hashes), 2000)
	})
	t.Run("field separation", func(t *testing.T) {
		cases := []struct {
			a, b *Policy
		}{
			{
				&Policy{From: "foo", Prefix: "bar"},
				&Policy{From: "f", Prefix: "oobar"},
			},
			{
				&Policy{From: "foo", Prefix: "bar"},
				&Policy{From: "foobar", Prefix: ""},
			},
			{
				&Policy{From: "foobar", Prefix: ""},
				&Policy{From: "", Prefix: "foobar"},
			},
			{
				&Policy{From: "foo", Prefix: "", Path: "bar"},
				&Policy{From: "foo", Prefix: "bar", Path: ""},
			},
			{
				&Policy{From: "", Prefix: "foo", Path: "bar"},
				&Policy{From: "foo", Prefix: "bar", Path: ""},
			},
			{
				&Policy{From: "", Prefix: "foo", Path: "bar"},
				&Policy{From: "foo", Prefix: "", Path: "bar"},
			},
		}
		for _, c := range cases {
			c.a.To = mustParseWeightedURLs(t, "https://foo")
			c.b.To = mustParseWeightedURLs(t, "https://foo")
		}

		for _, c := range cases {
			a, err := c.a.RouteID()
			require.NoError(t, err)
			b, err := c.b.RouteID()
			require.NoError(t, err)
			assert.NotEqual(t, a, b)
		}
	})
}

func randomPtr[T any](nilChance int, t T) *T {
	if mathrand.N(nilChance) == 0 {
		return nil
	}
	return &t
}