@ -1,3 +1,5 @@
//go:build !windows
/ *
Copyright The Helm Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
@ -16,7 +18,10 @@ limitations under the License.
package pusher
import (
"io"
"os"
"path/filepath"
"strings"
"testing"
"helm.sh/helm/v4/pkg/registry"
@ -94,3 +99,330 @@ func TestNewOCIPusher(t *testing.T) {
t . Errorf ( "Expected NewOCIPusher to contain %p as RegistryClient, got %p" , registryClient , op . opts . registryClient )
}
}
func TestOCIPusher_Push_ErrorHandling ( t * testing . T ) {
tests := [ ] struct {
name string
chartRef string
expectedError string
setupFunc func ( ) string
} {
{
name : "non-existent file" ,
chartRef : "/non/existent/file.tgz" ,
expectedError : "no such file" ,
} ,
{
name : "directory instead of file" ,
expectedError : "cannot push directory, must provide chart archive (.tgz)" ,
setupFunc : func ( ) string {
tempDir := t . TempDir ( )
return tempDir
} ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
pusher , err := NewOCIPusher ( )
if err != nil {
t . Fatal ( err )
}
chartRef := tt . chartRef
if tt . setupFunc != nil {
chartRef = tt . setupFunc ( )
}
err = pusher . Push ( chartRef , "oci://localhost:5000/test" )
if err == nil {
t . Fatal ( "Expected error but got none" )
}
if ! strings . Contains ( err . Error ( ) , tt . expectedError ) {
t . Errorf ( "Expected error containing %q, got %q" , tt . expectedError , err . Error ( ) )
}
} )
}
}
func TestOCIPusher_newRegistryClient ( t * testing . T ) {
cd := "../../testdata"
join := filepath . Join
ca , pub , priv := join ( cd , "rootca.crt" ) , join ( cd , "crt.pem" ) , join ( cd , "key.pem" )
tests := [ ] struct {
name string
opts [ ] Option
expectError bool
errorContains string
} {
{
name : "plain HTTP" ,
opts : [ ] Option { WithPlainHTTP ( true ) } ,
} ,
{
name : "with TLS client config" ,
opts : [ ] Option {
WithTLSClientConfig ( pub , priv , ca ) ,
} ,
} ,
{
name : "with insecure skip TLS verify" ,
opts : [ ] Option {
WithInsecureSkipTLSVerify ( true ) ,
} ,
} ,
{
name : "with cert and key only" ,
opts : [ ] Option {
WithTLSClientConfig ( pub , priv , "" ) ,
} ,
} ,
{
name : "with CA file only" ,
opts : [ ] Option {
WithTLSClientConfig ( "" , "" , ca ) ,
} ,
} ,
{
name : "default client without options" ,
opts : [ ] Option { } ,
} ,
{
name : "invalid cert file" ,
opts : [ ] Option {
WithTLSClientConfig ( "/non/existent/cert.pem" , priv , ca ) ,
} ,
expectError : true ,
errorContains : "can't create TLS config" ,
} ,
{
name : "invalid key file" ,
opts : [ ] Option {
WithTLSClientConfig ( pub , "/non/existent/key.pem" , ca ) ,
} ,
expectError : true ,
errorContains : "can't create TLS config" ,
} ,
{
name : "invalid CA file" ,
opts : [ ] Option {
WithTLSClientConfig ( "" , "" , "/non/existent/ca.crt" ) ,
} ,
expectError : true ,
errorContains : "can't create TLS config" ,
} ,
{
name : "combined TLS options" ,
opts : [ ] Option {
WithTLSClientConfig ( pub , priv , ca ) ,
WithInsecureSkipTLSVerify ( true ) ,
} ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
pusher , err := NewOCIPusher ( tt . opts ... )
if err != nil {
t . Fatal ( err )
}
op , ok := pusher . ( * OCIPusher )
if ! ok {
t . Fatal ( "Expected *OCIPusher" )
}
client , err := op . newRegistryClient ( )
if tt . expectError {
if err == nil {
t . Fatal ( "Expected error but got none" )
}
if tt . errorContains != "" && ! strings . Contains ( err . Error ( ) , tt . errorContains ) {
t . Errorf ( "Expected error containing %q, got %q" , tt . errorContains , err . Error ( ) )
}
} else {
if err != nil {
t . Fatalf ( "Unexpected error: %v" , err )
}
if client == nil {
t . Fatal ( "Expected non-nil registry client" )
}
}
} )
}
}
func TestOCIPusher_Push_ChartOperations ( t * testing . T ) {
// Path to test charts
chartPath := "../../pkg/cmd/testdata/testcharts/compressedchart-0.1.0.tgz"
chartWithProvPath := "../../pkg/cmd/testdata/testcharts/signtest-0.1.0.tgz"
tests := [ ] struct {
name string
chartRef string
href string
options [ ] Option
setupFunc func ( t * testing . T ) ( string , func ( ) )
expectError bool
errorContains string
} {
{
name : "invalid chart file" ,
chartRef : "../../pkg/action/testdata/charts/corrupted-compressed-chart.tgz" ,
href : "oci://localhost:5000/test" ,
expectError : true ,
errorContains : "does not appear to be a gzipped archive" ,
} ,
{
name : "chart read error" ,
setupFunc : func ( t * testing . T ) ( string , func ( ) ) {
t . Helper ( )
// Create a valid chart file that we'll make unreadable
tempDir := t . TempDir ( )
tempChart := filepath . Join ( tempDir , "temp-chart.tgz" )
// Copy a valid chart
src , err := os . Open ( chartPath )
if err != nil {
t . Fatal ( err )
}
defer src . Close ( )
dst , err := os . Create ( tempChart )
if err != nil {
t . Fatal ( err )
}
if _ , err := io . Copy ( dst , src ) ; err != nil {
t . Fatal ( err )
}
dst . Close ( )
// Make the file unreadable
if err := os . Chmod ( tempChart , 0000 ) ; err != nil {
t . Fatal ( err )
}
return tempChart , func ( ) {
os . Chmod ( tempChart , 0644 ) // Restore permissions for cleanup
}
} ,
href : "oci://localhost:5000/test" ,
expectError : true ,
errorContains : "permission denied" ,
} ,
{
name : "push with provenance file - loading phase" ,
chartRef : chartWithProvPath ,
href : "oci://registry.example.com/charts" ,
setupFunc : func ( t * testing . T ) ( string , func ( ) ) {
t . Helper ( )
// Copy chart and create a .prov file for it
tempDir := t . TempDir ( )
tempChart := filepath . Join ( tempDir , "signtest-0.1.0.tgz" )
tempProv := filepath . Join ( tempDir , "signtest-0.1.0.tgz.prov" )
// Copy chart file
src , err := os . Open ( chartWithProvPath )
if err != nil {
t . Fatal ( err )
}
defer src . Close ( )
dst , err := os . Create ( tempChart )
if err != nil {
t . Fatal ( err )
}
if _ , err := io . Copy ( dst , src ) ; err != nil {
t . Fatal ( err )
}
dst . Close ( )
// Create provenance file
if err := os . WriteFile ( tempProv , [ ] byte ( "test provenance data" ) , 0644 ) ; err != nil {
t . Fatal ( err )
}
return tempChart , func ( ) { }
} ,
expectError : true , // Will fail at the registry push step
errorContains : "" , // Error depends on registry client behavior
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
chartRef := tt . chartRef
var cleanup func ( )
if tt . setupFunc != nil {
chartRef , cleanup = tt . setupFunc ( t )
if cleanup != nil {
defer cleanup ( )
}
}
// Skip test if chart file doesn't exist and we're not expecting an error
if _ , err := os . Stat ( chartRef ) ; err != nil && ! tt . expectError {
t . Skipf ( "Test chart %s not found, skipping test" , chartRef )
}
pusher , err := NewOCIPusher ( tt . options ... )
if err != nil {
t . Fatal ( err )
}
err = pusher . Push ( chartRef , tt . href )
if tt . expectError {
if err == nil {
t . Fatal ( "Expected error but got none" )
}
if tt . errorContains != "" && ! strings . Contains ( err . Error ( ) , tt . errorContains ) {
t . Errorf ( "Expected error containing %q, got %q" , tt . errorContains , err . Error ( ) )
}
} else {
if err != nil {
t . Fatalf ( "Unexpected error: %v" , err )
}
}
} )
}
}
func TestOCIPusher_Push_MultipleOptions ( t * testing . T ) {
chartPath := "../../pkg/cmd/testdata/testcharts/compressedchart-0.1.0.tgz"
// Skip test if chart file doesn't exist
if _ , err := os . Stat ( chartPath ) ; err != nil {
t . Skipf ( "Test chart %s not found, skipping test" , chartPath )
}
pusher , err := NewOCIPusher ( )
if err != nil {
t . Fatal ( err )
}
// Test that multiple options are applied correctly
err = pusher . Push ( chartPath , "oci://localhost:5000/test" ,
WithPlainHTTP ( true ) ,
WithInsecureSkipTLSVerify ( true ) ,
)
// We expect an error since we're not actually pushing to a registry
if err == nil {
t . Fatal ( "Expected error when pushing without a valid registry" )
}
// Verify options were applied
op := pusher . ( * OCIPusher )
if ! op . opts . plainHTTP {
t . Error ( "Expected plainHTTP option to be applied" )
}
if ! op . opts . insecureSkipTLSverify {
t . Error ( "Expected insecureSkipTLSverify option to be applied" )
}
}