From ab4c97e6072c0402f91702afbe1eeffc6f92fb1a Mon Sep 17 00:00:00 2001 From: Feng Ruohang Date: Thu, 15 Oct 2026 21:33:02 +0800 Subject: [PATCH] fix: embed Console versioning error handling and cover checksum messages Signed-off-by: Feng Ruohang --- cmd/api-errors-checksum_test.go | 45 +++++++++++++++++ go.mod | 2 +- go.sum | 4 +- internal/hash/checksum-errors_test.go | 73 +++++++++++++++++++++++++++ 4 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 cmd/api-errors-checksum_test.go create mode 100644 internal/hash/checksum-errors_test.go diff --git a/cmd/api-errors-checksum_test.go b/cmd/api-errors-checksum_test.go new file mode 100644 index 000000000..3b1de75b5 --- /dev/null +++ b/cmd/api-errors-checksum_test.go @@ -0,0 +1,45 @@ +// Copyright (c) 2026 PGSTY +// +// This file is part of SILO Object Storage. +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package cmd + +import ( + "fmt" + "net/http" + "testing" + + "github.com/minio/minio/internal/hash" +) + +func TestChecksumMismatchAPIMessage(t *testing.T) { + for _, algorithm := range []string{"CRC32", "CRC32C", "SHA1", "SHA256", "CRC64NVME", ""} { + t.Run(algorithm, func(t *testing.T) { + mismatch := hash.ChecksumMismatch{Algorithm: algorithm, Want: "expected", Got: "actual"} + want := errorCodes.ToAPIErr(ErrContentChecksumMismatch).Description + if algorithm != "" { + want = fmt.Sprintf("The %s you specified did not match the calculated checksum.", algorithm) + } + for _, err := range []error{mismatch, fmt.Errorf("upload: %w", mismatch)} { + got := toAPIError(t.Context(), err) + if got.HTTPStatusCode != http.StatusBadRequest || got.Code != "XAmzContentChecksumMismatch" || got.Description != want { + t.Fatalf("toAPIError(%v) = %+v, want 400 XAmzContentChecksumMismatch: %s", err, got, want) + } + } + }) + } +} diff --git a/go.mod b/go.mod index 3a25fe190..c22798612 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.27.2 // Console and MC retain their historical module paths for best-effort upstream // compatibility. Pin the maintained PGSTY implementations used by SILO. -replace github.com/minio/console => github.com/pgsty/silo-console v0.0.0-20260916075814-1360e26d976d +replace github.com/minio/console => github.com/pgsty/silo-console v0.0.0-20261015132217-e5b165e4c0ee replace github.com/minio/mc => github.com/pgsty/mc v0.0.0-20260916070421-e952aa78f10a diff --git a/go.sum b/go.sum index 1b8f71b99..84b5f5a3a 100644 --- a/go.sum +++ b/go.sum @@ -547,8 +547,8 @@ github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwp github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pgsty/mc v0.0.0-20260916070421-e952aa78f10a h1:FY1vcTQT67HGBe23ozlhOiQnWCzUCGmeSN5x6DT/kFs= github.com/pgsty/mc v0.0.0-20260916070421-e952aa78f10a/go.mod h1:6z9lX8kOvaWz2vLlP7BjjF3JWHnIH86XDA2HwcmpZH8= -github.com/pgsty/silo-console v0.0.0-20260916075814-1360e26d976d h1:A9ou1GkUckGZd3BOgEXeZ393nbYST967SKcI4Lgr/Vs= -github.com/pgsty/silo-console v0.0.0-20260916075814-1360e26d976d/go.mod h1:JS165CF2yoTBIx1X8k1IQ6vunJcZFG3pRZlWHJ8UT8o= +github.com/pgsty/silo-console v0.0.0-20261015132217-e5b165e4c0ee h1:e51AnrGe0Zq+nHTYaGfv/GkehQMO9Mekuw/ew6zYwOM= +github.com/pgsty/silo-console v0.0.0-20261015132217-e5b165e4c0ee/go.mod h1:jpt39bx4wjhXoFB+BTOa/bx/pmU3pXzp2cT7PnT21Lo= github.com/pgsty/silo-pkg/v3 v3.14.1 h1:8MONT3Hky9EOnsuPk29590pEBE6MILaOnJtaejPPi7M= github.com/pgsty/silo-pkg/v3 v3.14.1/go.mod h1:jxKWxi52jSsDaDhXXPqjOyPDYdxx2iqdBXyLfM/xSIw= github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= diff --git a/internal/hash/checksum-errors_test.go b/internal/hash/checksum-errors_test.go new file mode 100644 index 000000000..e280318c4 --- /dev/null +++ b/internal/hash/checksum-errors_test.go @@ -0,0 +1,73 @@ +// Copyright (c) 2026 PGSTY +// +// This file is part of SILO Object Storage. +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package hash + +import ( + "bytes" + "errors" + "io" + "net/http" + "net/http/httptest" + "testing" +) + +func TestChecksumMismatchAlgorithm(t *testing.T) { + content := []byte("actual content") + for _, typ := range []ChecksumType{ChecksumCRC32, ChecksumCRC32C, ChecksumSHA1, ChecksumSHA256, ChecksumCRC64NVME} { + t.Run(typ.String(), func(t *testing.T) { + wrong := NewChecksumFromData(typ, []byte("wrong content")) + check := func(t *testing.T, err error) { + t.Helper() + var mismatch ChecksumMismatch + if !errors.As(err, &mismatch) || mismatch.Algorithm != typ.String() { + t.Fatalf("error=%+v, want %s checksum mismatch", err, typ) + } + } + check(t, wrong.Matches(content, 0)) + parts := NewChecksumFromData(typ, content) + parts.WantParts = 2 + check(t, parts.Matches(content, 1)) + for _, mode := range []string{"header", "trailer", "malformed-trailer"} { + t.Run(mode, func(t *testing.T) { + reader, err := NewReader(t.Context(), bytes.NewReader(content), int64(len(content)), "", "", int64(len(content))) + if err != nil { + t.Fatal(err) + } + req := httptest.NewRequest(http.MethodPut, "http://localhost/bucket/object", nil) + if mode == "header" { + req.Header.Set(typ.Key(), wrong.Encoded) + } else { + req.Header.Set("x-amz-trailer", typ.Key()) + req.Trailer = make(http.Header) + value := wrong.Encoded + if mode == "malformed-trailer" { + value = "!invalid-base64!" + } + req.Trailer.Set(typ.Key(), value) + } + if err = reader.AddChecksum(req, false); err != nil { + t.Fatal(err) + } + _, err = io.ReadAll(reader) + check(t, err) + }) + } + }) + } +}