@@ -22,6 +22,7 @@ import (
2222 "net/http/httptest"
2323 "os"
2424 "path/filepath"
25+ "strings"
2526 "testing"
2627
2728 "helm.sh/helm/v4/pkg/repo/v1/repotest"
@@ -506,3 +507,54 @@ func TestPullFileCompletion(t *testing.T) {
506507 checkFileCompletion (t , "pull" , false )
507508 checkFileCompletion (t , "pull repo/chart" , false )
508509}
510+
511+ // TestPullOCIWithTagAndDigest tests pulling an OCI chart with both tag and digest specified.
512+ // This is a regression test for https://github.com/helm/helm/issues/31600
513+ func TestPullOCIWithTagAndDigest (t * testing.T ) {
514+ srv := repotest .NewTempServer (
515+ t ,
516+ repotest .WithChartSourceGlob ("testdata/testcharts/*.tgz*" ),
517+ )
518+ defer srv .Stop ()
519+
520+ ociSrv , err := repotest .NewOCIServer (t , srv .Root ())
521+ if err != nil {
522+ t .Fatal (err )
523+ }
524+ result := ociSrv .RunWithReturn (t )
525+
526+ contentCache := t .TempDir ()
527+ outdir := t .TempDir ()
528+
529+ // Test: pull with tag and digest (the fixed bug from issue #31600)
530+ // Previously this failed with "encoding/hex: invalid byte: U+0073 's'"
531+ ref := fmt .Sprintf ("oci://%s/u/ocitestuser/oci-dependent-chart:0.1.0@%s" ,
532+ ociSrv .RegistryURL , result .PushedChart .Manifest .Digest )
533+
534+ cmd := fmt .Sprintf ("pull %s -d '%s' --registry-config %s --content-cache %s --plain-http" ,
535+ ref ,
536+ outdir ,
537+ filepath .Join (srv .Root (), "config.json" ),
538+ contentCache ,
539+ )
540+
541+ _ , _ , err = executeActionCommand (cmd )
542+ if err != nil {
543+ t .Fatalf ("pull with tag+digest failed: %v" , err )
544+ }
545+
546+ // Verify the file was downloaded
547+ // When digest is present, the filename uses the digest format (e.g. chart@sha256-hex.tgz)
548+ expectedFile := filepath .Join (outdir , "oci-dependent-chart-0.1.0.tgz" )
549+ if _ , err := os .Stat (expectedFile ); err != nil {
550+ // Try the digest-based filename; parse algorithm:hex to avoid fixed-offset assumptions
551+ algorithm , digestPart , ok := strings .Cut (result .PushedChart .Manifest .Digest , ":" )
552+ if ! ok {
553+ t .Fatalf ("digest must be in algorithm:hex format, got %q" , result .PushedChart .Manifest .Digest )
554+ }
555+ expectedFile = filepath .Join (outdir , fmt .Sprintf ("oci-dependent-chart@%s-%s.tgz" , algorithm , digestPart ))
556+ if _ , err := os .Stat (expectedFile ); err != nil {
557+ t .Errorf ("expected chart file not found: %v" , err )
558+ }
559+ }
560+ }
0 commit comments