2026-10-18 19:33:13 +02:00
package config
import (
"os"
"path/filepath"
2026-11-08 10:12:37 +01:00
"strings"
2026-10-18 19:33:13 +02:00
"testing"
"time"
)
func TestMirrorConfigValidate ( t * testing . T ) {
const digest = "sha256:0000000000000000000000000000000000000000000000000000000000000000"
tests := [ ] struct {
name string
schedule MirrorSchedule
wantErr bool
} {
{ name : "docker tags" , schedule : MirrorSchedule { Images : [ ] string { "pkg:docker/library/alpine@3.20" , "pkg:docker/nginx@stable" } , Interval : "6h" } } ,
{ name : "oci with tag" , schedule : MirrorSchedule { Images : [ ] string { "pkg:oci/app@v1?repository_url=ghcr.io/acme/app" } , Interval : "1m" } } ,
{ name : "oci digest with tag" , schedule : MirrorSchedule { Images : [ ] string { "pkg:oci/app@" + digest + "?repository_url=ghcr.io/acme/app&tag=v1" } , Interval : "1h" } } ,
{ name : "platforms" , schedule : MirrorSchedule { Images : [ ] string { "pkg:docker/alpine@3" } , Interval : "1h" , Platforms : [ ] string { "linux/amd64" , "linux/arm64/v8" } } } ,
{ name : "missing interval" , schedule : MirrorSchedule { Images : [ ] string { "pkg:docker/alpine@3" } } , wantErr : true } ,
{ name : "bad interval" , schedule : MirrorSchedule { Images : [ ] string { "pkg:docker/alpine@3" } , Interval : "often" } , wantErr : true } ,
{ name : "interval under a minute" , schedule : MirrorSchedule { Images : [ ] string { "pkg:docker/alpine@3" } , Interval : "30s" } , wantErr : true } ,
{ name : "no images" , schedule : MirrorSchedule { Interval : "1h" } , wantErr : true } ,
{ name : "unparsable purl" , schedule : MirrorSchedule { Images : [ ] string { "alpine:3" } , Interval : "1h" } , wantErr : true } ,
{ name : "not an image" , schedule : MirrorSchedule { Images : [ ] string { "pkg:npm/lodash@4.17.21" } , Interval : "1h" } , wantErr : true } ,
{ name : "no tag" , schedule : MirrorSchedule { Images : [ ] string { "pkg:docker/alpine" } , Interval : "1h" } , wantErr : true } ,
{ name : "docker digest" , schedule : MirrorSchedule { Images : [ ] string { "pkg:docker/alpine@" + digest } , Interval : "1h" } , wantErr : true } ,
{ name : "docker digest with tag qualifier" , schedule : MirrorSchedule { Images : [ ] string { "pkg:docker/alpine@" + digest + "?tag=3" } , Interval : "1h" } , wantErr : true } ,
{ name : "oci digest only" , schedule : MirrorSchedule { Images : [ ] string { "pkg:oci/app@" + digest + "?repository_url=ghcr.io/acme/app" } , Interval : "1h" } , wantErr : true } ,
{ name : "oci without repository_url" , schedule : MirrorSchedule { Images : [ ] string { "pkg:oci/app@v1" } , Interval : "1h" } , wantErr : true } ,
{ name : "platform without architecture" , schedule : MirrorSchedule { Images : [ ] string { "pkg:docker/alpine@3" } , Interval : "1h" , Platforms : [ ] string { "linux" } } , wantErr : true } ,
{ name : "platform list in one entry" , schedule : MirrorSchedule { Images : [ ] string { "pkg:docker/alpine@3" } , Interval : "1h" , Platforms : [ ] string { "linux/amd64,linux/arm64" } } , wantErr : true } ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
m := MirrorConfig { Schedules : [ ] MirrorSchedule { tt . schedule } }
err := m . Validate ( )
if ( err != nil ) != tt . wantErr {
t . Errorf ( "Validate() error = %v, wantErr %v" , err , tt . wantErr )
}
} )
}
}
func TestMirrorScheduleIntervalDuration ( t * testing . T ) {
m := MirrorConfig { Schedules : [ ] MirrorSchedule { { Images : [ ] string { "pkg:docker/alpine@3" } , Interval : "6h" } } }
if err := m . Validate ( ) ; err != nil {
t . Fatalf ( "Validate: %v" , err )
}
if got := m . Schedules [ 0 ] . IntervalDuration ( ) ; got != 6 * time . Hour {
t . Errorf ( "IntervalDuration() = %v, want 6h" , got )
}
}
func TestValidateRejectsMirrorSchedulesWithoutCachedArtifacts ( t * testing . T ) {
cfg := Default ( )
cfg . Mirror . Schedules = [ ] MirrorSchedule { { Images : [ ] string { "pkg:docker/alpine@3" } , Interval : "1h" } }
cfg . Storage . CacheArtifacts = false
if err := cfg . Validate ( ) ; err == nil {
t . Error ( "Validate() accepted mirror.schedules with storage.cache_artifacts: false" )
}
}
func TestLoadYAMLMirrorSchedules ( t * testing . T ) {
path := filepath . Join ( t . TempDir ( ) , "config.yaml" )
data := `
mirror :
schedules :
- images : [ pkg : docker / library / alpine @ 3.20 ]
interval : 6 h
platforms : [ linux / amd64 ]
- images : [ pkg : docker / nginx @ stable ]
interval : 30 s
`
if err := os . WriteFile ( path , [ ] byte ( data ) , 0 o600 ) ; err != nil {
t . Fatal ( err )
}
cfg , err := Load ( path )
if err != nil {
t . Fatalf ( "Load: %v" , err )
}
if len ( cfg . Mirror . Schedules ) != 2 || cfg . Mirror . Schedules [ 0 ] . Platforms [ 0 ] != "linux/amd64" {
t . Fatalf ( "schedules = %+v" , cfg . Mirror . Schedules )
}
if err := cfg . Validate ( ) ; err == nil {
t . Error ( "Validate() accepted a 30s interval in the second schedule" )
}
}
2026-10-31 13:53:37 +01:00
func TestMirrorRefreshValidate ( t * testing . T ) {
tests := [ ] struct {
name string
refresh * MirrorRefresh
wantErr bool
} {
{ name : "defaults" , refresh : & MirrorRefresh { Interval : "24h" } } ,
{ name : "all fields" , refresh : & MirrorRefresh { Interval : "12h" , Top : 20 , Since : "14d" , MaxVersions : 100 , MaxBytes : "2GB" } } ,
{ name : "missing interval" , refresh : & MirrorRefresh { } , wantErr : true } ,
{ name : "interval under a minute" , refresh : & MirrorRefresh { Interval : "10s" } , wantErr : true } ,
{ name : "negative top" , refresh : & MirrorRefresh { Interval : "1h" , Top : - 1 } , wantErr : true } ,
{ name : "bad since" , refresh : & MirrorRefresh { Interval : "1h" , Since : "recently" } , wantErr : true } ,
{ name : "negative since" , refresh : & MirrorRefresh { Interval : "1h" , Since : "-1d" } , wantErr : true } ,
{ name : "negative max_versions" , refresh : & MirrorRefresh { Interval : "1h" , MaxVersions : - 1 } , wantErr : true } ,
{ name : "bad max_bytes" , refresh : & MirrorRefresh { Interval : "1h" , MaxBytes : "lots" } , wantErr : true } ,
2026-11-05 14:53:36 +01:00
{ name : "negative max_bytes" , refresh : & MirrorRefresh { Interval : "1h" , MaxBytes : "-1GB" } , wantErr : true } ,
2026-10-31 13:53:37 +01:00
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
m := MirrorConfig { Refresh : tt . refresh }
err := m . Validate ( )
if ( err != nil ) != tt . wantErr {
t . Errorf ( "Validate() error = %v, wantErr %v" , err , tt . wantErr )
}
} )
}
}
func TestMirrorRefreshDefaults ( t * testing . T ) {
m := MirrorConfig { Refresh : & MirrorRefresh { Interval : "24h" } }
if err := m . Validate ( ) ; err != nil {
t . Fatalf ( "Validate: %v" , err )
}
r := m . Refresh
if r . IntervalDuration ( ) != 24 * time . Hour || r . TopPackages ( ) != 50 || r . SinceDuration ( ) != 14 * 24 * time . Hour ||
r . MaxVersionsPerRun ( ) != 0 || r . MaxBytesPerRun ( ) != 0 {
t . Errorf ( "got interval %v top %d since %v max_versions %d max_bytes %d, want 24h 50 336h 0 0" ,
r . IntervalDuration ( ) , r . TopPackages ( ) , r . SinceDuration ( ) , r . MaxVersionsPerRun ( ) , r . MaxBytesPerRun ( ) )
}
}
func TestValidateRejectsMirrorRefreshWithoutCachedArtifacts ( t * testing . T ) {
cfg := Default ( )
cfg . Mirror . Refresh = & MirrorRefresh { Interval : "24h" }
cfg . Storage . CacheArtifacts = false
if err := cfg . Validate ( ) ; err == nil {
t . Error ( "Validate() accepted mirror.refresh with storage.cache_artifacts: false" )
}
}
func TestLoadYAMLMirrorRefresh ( t * testing . T ) {
path := filepath . Join ( t . TempDir ( ) , "config.yaml" )
data := `
mirror :
refresh :
interval : 12 h
top : 10
since : 7 d
max_versions : 200
max_bytes : 1 GB
`
if err := os . WriteFile ( path , [ ] byte ( data ) , 0 o600 ) ; err != nil {
t . Fatal ( err )
}
cfg , err := Load ( path )
if err != nil {
t . Fatalf ( "Load: %v" , err )
}
r := cfg . Mirror . Refresh
if r == nil {
t . Fatal ( "mirror.refresh not loaded" )
}
if err := cfg . Validate ( ) ; err != nil {
t . Fatalf ( "Validate: %v" , err )
}
if r . IntervalDuration ( ) != 12 * time . Hour || r . TopPackages ( ) != 10 || r . SinceDuration ( ) != 7 * 24 * time . Hour ||
r . MaxVersionsPerRun ( ) != 200 || r . MaxBytesPerRun ( ) != 1 << 30 {
t . Errorf ( "got interval %v top %d since %v max_versions %d max_bytes %d" ,
r . IntervalDuration ( ) , r . TopPackages ( ) , r . SinceDuration ( ) , r . MaxVersionsPerRun ( ) , r . MaxBytesPerRun ( ) )
}
}
2026-11-08 10:12:37 +01:00
func TestWarningsMirrorRefreshWithoutCacheMetadata ( t * testing . T ) {
cfg := Default ( )
cfg . Mirror . Refresh = & MirrorRefresh { Interval : "24h" }
cfg . CacheMetadata = false
if err := cfg . Validate ( ) ; err != nil {
t . Fatalf ( "Validate() = %v, want mirror.refresh accepted for the other ecosystems" , err )
}
if got := cfg . Warnings ( ) ; len ( got ) != 1 || ! strings . Contains ( got [ 0 ] , "cache_metadata" ) {
t . Errorf ( "Warnings() = %q, want one about cache_metadata" , got )
}
cfg . CacheMetadata = true
if got := cfg . Warnings ( ) ; len ( got ) != 0 {
t . Errorf ( "Warnings() with cache_metadata = %q, want none" , got )
}
}