Pull request 1979: AG-25263 filtering config

Squashed commit of the following:

commit a5607f811ab4642a0e34d3042240e3a9aafc3d84
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Aug 30 15:44:26 2023 +0300

    home: upgrade yaml

commit 0593e4da17613b8770c4567fdd5fbfde31631e6f
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Aug 30 15:01:14 2023 +0300

    home: upgrade yaml

commit 59ec4ba0bbe59fb2e95290f4f8101891ce4ff959
Merge: 6555941f5 a325c9b6b
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Aug 30 14:57:41 2023 +0300

    Merge remote-tracking branch 'origin/master' into AG-25263-filtering-config

    # Conflicts:
    #	internal/dnsforward/http.go
    #	internal/dnsforward/http_test.go
    #	internal/dnsforward/process.go
    #	internal/dnsforward/process_internal_test.go

commit 6555941f57e99aa8bc2473b8be9e4f096ce665e0
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Aug 30 14:38:01 2023 +0300

    docs: changelog

commit c66d14cecbb1f08869a90c46e58d08ec03e29de5
Merge: a50ff1622 aac36a2d2
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Aug 30 12:58:41 2023 +0300

    Merge remote-tracking branch 'origin/master' into AG-25263-filtering-config

commit a50ff162291f7953912c1bb195bd7b5d802d0ab3
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Wed Aug 30 11:22:12 2023 +0300

    home: imp code

commit ef40d073e00d6acb164c0d680ed9971c4e75fbb7
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Aug 29 16:19:14 2023 +0300

    home: imp code

commit 5fa09a95e901a72dc5d2a90ba47c96842152587b
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Aug 29 14:47:48 2023 +0300

    home: imp code

commit 52bb295120f468faf94c953d0e795d1d58696ef1
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Aug 29 13:33:01 2023 +0300

    home: imp code

commit 24cfccf071385d660aeba4ced744ebbacc819686
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Aug 29 12:24:20 2023 +0300

    filtering: imp code

commit 758242b69ee5f351b25407340f989ec6b90d3607
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Tue Aug 29 10:32:51 2023 +0300

    home: imp code

commit 906deaa3bf86af5038fb05a54c34cc9511500ac9
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Aug 28 14:46:01 2023 +0300

    dnsforward: imp code

commit 978bb508349cd0b6a1a0bd3df5b879b6a4dd9e29
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Aug 28 14:40:31 2023 +0300

    home: imp code

commit d5b8dd5c930b0ccbe612f320a9908f2849370fd4
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Aug 28 12:34:32 2023 +0300

    filtering: imp code

commit d3e5726df6bc93112329ec81bc83932ccf64b8de
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Aug 28 11:57:33 2023 +0300

    home: upgrade yaml

commit 75d701553595786d5695540c001e6d46153351a9
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Aug 28 11:46:55 2023 +0300

    home: upgrade yaml

commit cd8cb623b0e6eb00fd05fb62b7445ca4315b94e0
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Aug 28 11:29:16 2023 +0300

    docs: changelog

commit 31c098d6f8d68a4afa30246c8d067a1ef4586de9
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Mon Aug 28 11:08:48 2023 +0300

    docs: changelog

commit 24c88dd5d42b0168a13ea11b3f760eae1e804558
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Sun Aug 27 19:12:55 2023 +0300

    home: imp code

commit 94f2d386c9c1b6be5df06525078c90a3d068641f
Author: Dimitry Kolyshev <dkolyshev@adguard.com>
Date:   Sun Aug 27 16:15:05 2023 +0300

    home: upgrade yaml

... and 10 more commits
This commit is contained in:
Dimitry Kolyshev
2023-08-30 18:26:02 +03:00
committed by Ainar Garipov
parent a325c9b6bb
commit a2ca8b5b4a
20 changed files with 664 additions and 320 deletions

View File

@@ -131,7 +131,8 @@ type configuration struct {
WhitelistFilters []filtering.FilterYAML `yaml:"whitelist_filters"`
UserRules []string `yaml:"user_rules"`
DHCP *dhcpd.ServerConfig `yaml:"dhcp"`
DHCP *dhcpd.ServerConfig `yaml:"dhcp"`
Filtering *filtering.Config `yaml:"filtering"`
// Clients contains the YAML representations of the persistent clients.
// This field is only used for reading and writing persistent client data.
@@ -185,9 +186,10 @@ type dnsConfig struct {
// in query log and statistics.
AnonymizeClientIP bool `yaml:"anonymize_client_ip"`
dnsforward.FilteringConfig `yaml:",inline"`
DnsfilterConf *filtering.Config `yaml:",inline"`
// Config is the embed configuration with DNS params.
//
// TODO(a.garipov): Remove embed.
dnsforward.Config `yaml:",inline"`
// UpstreamTimeout is the timeout for querying upstream servers.
UpstreamTimeout timeutil.Duration `yaml:"upstream_timeout"`
@@ -295,14 +297,11 @@ var config = &configuration{
DNS: dnsConfig{
BindHosts: []netip.Addr{netip.IPv4Unspecified()},
Port: defaultPortDNS,
FilteringConfig: dnsforward.FilteringConfig{
ProtectionEnabled: true, // whether or not use any of filtering features
BlockingMode: dnsforward.BlockingModeDefault,
BlockedResponseTTL: 10, // in seconds
Ratelimit: 20,
RefuseAny: true,
AllServers: false,
HandleDDR: true,
Config: dnsforward.Config{
Ratelimit: 20,
RefuseAny: true,
AllServers: false,
HandleDDR: true,
FastestTimeout: timeutil.Duration{
Duration: fastip.DefaultPingWaitTimeout,
},
@@ -322,33 +321,6 @@ var config = &configuration{
// was later increased to 300 due to https://github.com/AdguardTeam/AdGuardHome/issues/2257
MaxGoroutines: 300,
},
DnsfilterConf: &filtering.Config{
FilteringEnabled: true,
FiltersUpdateIntervalHours: 24,
ParentalEnabled: false,
SafeBrowsingEnabled: false,
SafeBrowsingCacheSize: 1 * 1024 * 1024,
SafeSearchCacheSize: 1 * 1024 * 1024,
ParentalCacheSize: 1 * 1024 * 1024,
CacheTime: 30,
SafeSearchConf: filtering.SafeSearchConfig{
Enabled: false,
Bing: true,
DuckDuckGo: true,
Google: true,
Pixabay: true,
Yandex: true,
YouTube: true,
},
BlockedServices: &filtering.BlockedServices{
Schedule: schedule.EmptyWeekly(),
IDs: []string{},
},
},
UpstreamTimeout: timeutil.Duration{Duration: dnsforward.DefaultTimeout},
UsePrivateRDNS: true,
},
@@ -385,6 +357,37 @@ var config = &configuration{
URL: "https://adguardteam.github.io/HostlistsRegistry/assets/filter_2.txt",
Name: "AdAway Default Blocklist",
}},
Filtering: &filtering.Config{
ProtectionEnabled: true,
BlockingMode: filtering.BlockingModeDefault,
BlockedResponseTTL: 10, // in seconds
FilteringEnabled: true,
FiltersUpdateIntervalHours: 24,
ParentalEnabled: false,
SafeBrowsingEnabled: false,
SafeBrowsingCacheSize: 1 * 1024 * 1024,
SafeSearchCacheSize: 1 * 1024 * 1024,
ParentalCacheSize: 1 * 1024 * 1024,
CacheTime: 30,
SafeSearchConf: filtering.SafeSearchConfig{
Enabled: false,
Bing: true,
DuckDuckGo: true,
Google: true,
Pixabay: true,
Yandex: true,
YouTube: true,
},
BlockedServices: &filtering.BlockedServices{
Schedule: schedule.EmptyWeekly(),
IDs: []string{},
},
},
DHCP: &dhcpd.ServerConfig{
LocalDomainName: "lan",
Conf4: dhcpd.V4ServerConf{
@@ -493,8 +496,8 @@ func parseConfig() (err error) {
return fmt.Errorf("validating udp ports: %w", err)
}
if !filtering.ValidateUpdateIvl(config.DNS.DnsfilterConf.FiltersUpdateIntervalHours) {
config.DNS.DnsfilterConf.FiltersUpdateIntervalHours = 24
if !filtering.ValidateUpdateIvl(config.Filtering.FiltersUpdateIntervalHours) {
config.Filtering.FiltersUpdateIntervalHours = 24
}
if config.DNS.UpstreamTimeout.Duration == 0 {
@@ -574,17 +577,17 @@ func (c *configuration) write() (err error) {
}
if Context.filters != nil {
Context.filters.WriteDiskConfig(config.DNS.DnsfilterConf)
config.Filters = config.DNS.DnsfilterConf.Filters
config.WhitelistFilters = config.DNS.DnsfilterConf.WhitelistFilters
config.UserRules = config.DNS.DnsfilterConf.UserRules
Context.filters.WriteDiskConfig(config.Filtering)
config.Filters = config.Filtering.Filters
config.WhitelistFilters = config.Filtering.WhitelistFilters
config.UserRules = config.Filtering.UserRules
}
if s := Context.dnsServer; s != nil {
c := dnsforward.FilteringConfig{}
c := dnsforward.Config{}
s.WriteDiskConfig(&c)
dns := &config.DNS
dns.FilteringConfig = c
dns.Config = c
dns.LocalPTRResolvers = s.LocalPTRResolvers()

View File

@@ -127,12 +127,12 @@ func handleStatus(w http.ResponseWriter, r *http.Request) {
}
var (
fltConf *dnsforward.FilteringConfig
fltConf *dnsforward.Config
protectionDisabledUntil *time.Time
protectionEnabled bool
)
if Context.dnsServer != nil {
fltConf = &dnsforward.FilteringConfig{}
fltConf = &dnsforward.Config{}
Context.dnsServer.WriteDiskConfig(fltConf)
protectionEnabled, protectionDisabledUntil = Context.dnsServer.UpdatedProtectionStatus()
}

View File

@@ -94,7 +94,7 @@ func initDNS() (err error) {
return fmt.Errorf("init querylog: %w", err)
}
Context.filters, err = filtering.New(config.DNS.DnsfilterConf, nil)
Context.filters, err = filtering.New(config.Filtering, nil)
if err != nil {
// Don't wrap the error, since it's informative enough as is.
return err
@@ -230,13 +230,13 @@ func newServerConfig(
hosts := aghalg.CoalesceSlice(dnsConf.BindHosts, []netip.Addr{netutil.IPv4Localhost()})
newConf = &dnsforward.ServerConfig{
UDPListenAddrs: ipsToUDPAddrs(hosts, dnsConf.Port),
TCPListenAddrs: ipsToTCPAddrs(hosts, dnsConf.Port),
FilteringConfig: dnsConf.FilteringConfig,
ConfigModified: onConfigModified,
HTTPRegister: httpReg,
UseDNS64: config.DNS.UseDNS64,
DNS64Prefixes: config.DNS.DNS64Prefixes,
UDPListenAddrs: ipsToUDPAddrs(hosts, dnsConf.Port),
TCPListenAddrs: ipsToTCPAddrs(hosts, dnsConf.Port),
Config: dnsConf.Config,
ConfigModified: onConfigModified,
HTTPRegister: httpReg,
UseDNS64: config.DNS.UseDNS64,
DNS64Prefixes: config.DNS.DNS64Prefixes,
}
var initialAddresses []netip.Addr

View File

@@ -272,7 +272,7 @@ func setupOpts(opts options) (err error) {
// initContextClients initializes Context clients and related fields.
func initContextClients() (err error) {
err = setupDNSFilteringConf(config.DNS.DnsfilterConf)
err = setupDNSFilteringConf(config.Filtering)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
@@ -303,7 +303,7 @@ func initContextClients() (err error) {
Context.dhcpServer,
Context.etcHosts,
arpDB,
config.DNS.DnsfilterConf,
config.Filtering,
)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
@@ -365,6 +365,9 @@ func setupDNSFilteringConf(conf *filtering.Config) (err error) {
pcService = "parental control"
defaultParentalServer = `https://family.adguard-dns.com/dns-query`
pcTXTSuffix = `pc.dns.adguard.com.`
defaultSafeBrowsingBlockHost = "standard-block.dns.adguard.com"
defaultParentalBlockHost = "family-block.dns.adguard.com"
)
conf.EtcHosts = Context.etcHosts
@@ -401,6 +404,10 @@ func setupDNSFilteringConf(conf *filtering.Config) (err error) {
CacheSize: conf.SafeBrowsingCacheSize,
})
if conf.SafeBrowsingBlockHost != "" {
conf.SafeBrowsingBlockHost = defaultSafeBrowsingBlockHost
}
parUps, err := upstream.AddressToUpstream(defaultParentalServer, upsOpts)
if err != nil {
return fmt.Errorf("converting parental server: %w", err)
@@ -414,6 +421,10 @@ func setupDNSFilteringConf(conf *filtering.Config) (err error) {
CacheSize: conf.ParentalCacheSize,
})
if conf.ParentalBlockHost != "" {
conf.ParentalBlockHost = defaultParentalBlockHost
}
conf.SafeSearchConf.CustomResolver = safeSearchResolver{}
conf.SafeSearch, err = safesearch.NewDefault(
conf.SafeSearchConf,
@@ -544,7 +555,7 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}) {
fatalOnError(err)
upd := updater.NewUpdater(&updater.Config{
Client: config.DNS.DnsfilterConf.HTTPClient,
Client: config.Filtering.HTTPClient,
Version: version.Version(),
Channel: version.Channel(),
GOARCH: runtime.GOARCH,

View File

@@ -23,7 +23,7 @@ import (
)
// currentSchemaVersion is the current schema version.
const currentSchemaVersion = 25
const currentSchemaVersion = 26
// These aliases are provided for convenience.
type (
@@ -100,6 +100,7 @@ func upgradeConfigSchema(oldVersion int, diskConf yobj) (err error) {
upgradeSchema22to23,
upgradeSchema23to24,
upgradeSchema24to25,
upgradeSchema25to26,
}
n := 0
@@ -1425,34 +1426,172 @@ func upgradeSchema24to25(diskConf yobj) (err error) {
return nil
}
// moveField gets field value for key from diskConf, and then set this value
// in newConf for newKey.
func moveField[T any](diskConf, newConf yobj, key, newKey string) (err error) {
ok, newVal, err := fieldValue[T](diskConf, key)
// upgradeSchema25to26 performs the following changes:
//
// # BEFORE:
// 'dns':
// 'filtering_enabled': true
// 'filters_update_interval': 24
// 'parental_enabled': false
// 'safebrowsing_enabled': false
// 'safebrowsing_cache_size': 1048576
// 'safesearch_cache_size': 1048576
// 'parental_cache_size': 1048576
// 'safe_search':
// 'enabled': false
// 'bing': true
// 'duckduckgo': true
// 'google': true
// 'pixabay': true
// 'yandex': true
// 'youtube': true
// 'rewrites': []
// 'blocked_services':
// 'schedule':
// 'time_zone': 'Local'
// 'ids': []
// 'protection_enabled': true,
// 'blocking_mode': 'custom_ip',
// 'blocking_ipv4': '1.2.3.4',
// 'blocking_ipv6': '1:2:3::4',
// 'blocked_response_ttl': 10,
// 'protection_disabled_until': 'null',
// 'parental_block_host': 'p.dns.adguard.com',
// 'safebrowsing_block_host': 's.dns.adguard.com',
// ...
//
// # AFTER:
// 'filtering':
// 'filtering_enabled': true
// 'filters_update_interval': 24
// 'parental_enabled': false
// 'safebrowsing_enabled': false
// 'safebrowsing_cache_size': 1048576
// 'safesearch_cache_size': 1048576
// 'parental_cache_size': 1048576
// 'safe_search':
// 'enabled': false
// 'bing': true
// 'duckduckgo': true
// 'google': true
// 'pixabay': true
// 'yandex': true
// 'youtube': true
// 'rewrites': []
// 'blocked_services':
// 'schedule':
// 'time_zone': 'Local'
// 'ids': []
// 'protection_enabled': true,
// 'blocking_mode': 'custom_ip',
// 'blocking_ipv4': '1.2.3.4',
// 'blocking_ipv6': '1:2:3::4',
// 'blocked_response_ttl': 10,
// 'protection_disabled_until': 'null',
// 'parental_block_host': 'p.dns.adguard.com',
// 'safebrowsing_block_host': 's.dns.adguard.com',
// 'dns'
// ...
func upgradeSchema25to26(diskConf yobj) (err error) {
log.Printf("Upgrade yaml: 25 to 26")
diskConf["schema_version"] = 26
dnsVal, ok := diskConf["dns"]
if !ok {
return nil
}
dnsObj, ok := dnsVal.(yobj)
if !ok {
return fmt.Errorf("unexpected type of dns: %T", dnsVal)
}
filteringObj := yobj{}
err = coalesceError(
moveFieldValue[bool](dnsObj, filteringObj, "filtering_enabled"),
moveFieldValue[int](dnsObj, filteringObj, "filters_update_interval"),
moveFieldValue[bool](dnsObj, filteringObj, "parental_enabled"),
moveFieldValue[bool](dnsObj, filteringObj, "safebrowsing_enabled"),
moveFieldValue[int](dnsObj, filteringObj, "safebrowsing_cache_size"),
moveFieldValue[int](dnsObj, filteringObj, "safesearch_cache_size"),
moveFieldValue[int](dnsObj, filteringObj, "parental_cache_size"),
moveFieldValue[yobj](dnsObj, filteringObj, "safe_search"),
moveFieldValue[yarr](dnsObj, filteringObj, "rewrites"),
moveFieldValue[yobj](dnsObj, filteringObj, "blocked_services"),
moveFieldValue[bool](dnsObj, filteringObj, "protection_enabled"),
moveFieldValue[string](dnsObj, filteringObj, "blocking_mode"),
moveFieldValue[string](dnsObj, filteringObj, "blocking_ipv4"),
moveFieldValue[string](dnsObj, filteringObj, "blocking_ipv6"),
moveFieldValue[int](dnsObj, filteringObj, "blocked_response_ttl"),
moveFieldValue[any](dnsObj, filteringObj, "protection_disabled_until"),
moveFieldValue[string](dnsObj, filteringObj, "parental_block_host"),
moveFieldValue[string](dnsObj, filteringObj, "safebrowsing_block_host"),
)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
}
switch v := newVal.(type) {
case int, bool, string:
newConf[newKey] = v
default:
return fmt.Errorf("invalid type of %s: %T", key, newVal)
if len(filteringObj) != 0 {
diskConf["filtering"] = filteringObj
}
delete(dnsObj, "filtering_enabled")
delete(dnsObj, "filters_update_interval")
delete(dnsObj, "parental_enabled")
delete(dnsObj, "safebrowsing_enabled")
delete(dnsObj, "safebrowsing_cache_size")
delete(dnsObj, "safesearch_cache_size")
delete(dnsObj, "parental_cache_size")
delete(dnsObj, "safe_search")
delete(dnsObj, "rewrites")
delete(dnsObj, "blocked_services")
delete(dnsObj, "protection_enabled")
delete(dnsObj, "blocking_mode")
delete(dnsObj, "blocking_ipv4")
delete(dnsObj, "blocking_ipv6")
delete(dnsObj, "blocked_response_ttl")
delete(dnsObj, "protection_disabled_until")
delete(dnsObj, "parental_block_host")
delete(dnsObj, "safebrowsing_block_host")
return nil
}
// fieldValue returns the value of type T for key in diskConf object.
func fieldValue[T any](diskConf yobj, key string) (ok bool, field any, err error) {
fieldVal, ok := diskConf[key]
// moveField gets field value for key from fromObj, and then sets this value in
// newConf for newKey.
func moveField[T any](fromObj, newConf yobj, key, newKey string) (err error) {
ok, newVal, err := fieldValue[T](fromObj, key)
if !ok {
return false, new(T), nil
return err
}
newConf[newKey] = newVal
return nil
}
// moveFieldValue gets field value for key from fromObj, and then sets this
// value in newConf with the same key.
func moveFieldValue[T any](fromObj, newConf yobj, key string) (err error) {
return moveField[T](fromObj, newConf, key, key)
}
// fieldValue returns the value of type T for key in confObj object. Returns
// nil for fields with nil values.
func fieldValue[T any](confObj yobj, key string) (ok bool, field T, err error) {
fieldVal, ok := confObj[key]
if !ok {
return false, field, nil
}
if fieldVal == nil {
return true, field, nil
}
f, ok := fieldVal.(T)
if !ok {
return false, nil, fmt.Errorf("unexpected type of %s: %T", key, fieldVal)
return false, field, fmt.Errorf("unexpected type of %s: %T", key, fieldVal)
}
return true, f, nil

View File

@@ -1466,3 +1466,102 @@ func TestUpgradeSchema24to25(t *testing.T) {
})
}
}
func TestUpgradeSchema25to26(t *testing.T) {
const newSchemaVer = 26
testCases := []struct {
in yobj
want yobj
name string
}{{
name: "empty",
in: yobj{},
want: yobj{
"schema_version": newSchemaVer,
},
}, {
name: "ok",
in: yobj{
"dns": yobj{
"filtering_enabled": true,
"filters_update_interval": 24,
"parental_enabled": false,
"safebrowsing_enabled": false,
"safebrowsing_cache_size": 1048576,
"safesearch_cache_size": 1048576,
"parental_cache_size": 1048576,
"safe_search": yobj{
"enabled": false,
"bing": true,
"duckduckgo": true,
"google": true,
"pixabay": true,
"yandex": true,
"youtube": true,
},
"rewrites": yarr{},
"blocked_services": yobj{
"schedule": yobj{
"time_zone": "Local",
},
"ids": yarr{},
},
"protection_enabled": true,
"blocking_mode": "custom_ip",
"blocking_ipv4": "1.2.3.4",
"blocking_ipv6": "1:2:3::4",
"blocked_response_ttl": 10,
"protection_disabled_until": nil,
"parental_block_host": "p.dns.adguard.com",
"safebrowsing_block_host": "s.dns.adguard.com",
},
},
want: yobj{
"dns": yobj{},
"filtering": yobj{
"filtering_enabled": true,
"filters_update_interval": 24,
"parental_enabled": false,
"safebrowsing_enabled": false,
"safebrowsing_cache_size": 1048576,
"safesearch_cache_size": 1048576,
"parental_cache_size": 1048576,
"safe_search": yobj{
"enabled": false,
"bing": true,
"duckduckgo": true,
"google": true,
"pixabay": true,
"yandex": true,
"youtube": true,
},
"rewrites": yarr{},
"blocked_services": yobj{
"schedule": yobj{
"time_zone": "Local",
},
"ids": yarr{},
},
"protection_enabled": true,
"blocking_mode": "custom_ip",
"blocking_ipv4": "1.2.3.4",
"blocking_ipv6": "1:2:3::4",
"blocked_response_ttl": 10,
"protection_disabled_until": nil,
"parental_block_host": "p.dns.adguard.com",
"safebrowsing_block_host": "s.dns.adguard.com",
},
"schema_version": newSchemaVer,
},
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := upgradeSchema25to26(tc.in)
require.NoError(t, err)
assert.Equal(t, tc.want, tc.in)
})
}
}