feat: add checksum for git downloader
This commit is contained in:
		| @@ -11,7 +11,7 @@ | |||||||
|     <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> |     <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> | ||||||
|         <text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text> |         <text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text> | ||||||
|         <text x="33.5" y="14">coverage</text> |         <text x="33.5" y="14">coverage</text> | ||||||
|         <text x="86" y="15" fill="#010101" fill-opacity=".3">18.9%</text> |         <text x="86" y="15" fill="#010101" fill-opacity=".3">20.2%</text> | ||||||
|         <text x="86" y="14">18.9%</text> |         <text x="86" y="14">20.2%</text> | ||||||
|     </g> |     </g> | ||||||
| </svg> | </svg> | ||||||
|   | |||||||
| Before Width: | Height: | Size: 926 B After Width: | Height: | Size: 926 B | 
| @@ -20,11 +20,12 @@ import ( | |||||||
| 	"context" | 	"context" | ||||||
| 	"encoding/hex" | 	"encoding/hex" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"log/slog" | ||||||
| 	"os" | 	"os" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/dl" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dl" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dlcache" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type SourceDownloader struct { | type SourceDownloader struct { | ||||||
| @@ -76,6 +77,7 @@ func (s *SourceDownloader) DownloadSources( | |||||||
|  |  | ||||||
| 		opts.DlCache = dlcache.New(s.cfg) | 		opts.DlCache = dlcache.New(s.cfg) | ||||||
|  |  | ||||||
|  | 		slog.Warn("opts", "opts", opts) | ||||||
| 		err := dl.Download(ctx, opts) | 		err := dl.Download(ctx, opts) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
|   | |||||||
| @@ -355,30 +355,6 @@ msgid "" | |||||||
| "Database version does not exist. Run alr fix if something isn't working." | "Database version does not exist. Run alr fix if something isn't working." | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: internal/dl/dl.go:170 |  | ||||||
| msgid "Source can be updated, updating if required" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/dl/dl.go:201 |  | ||||||
| msgid "Source found in cache and linked to destination" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/dl/dl.go:208 |  | ||||||
| msgid "Source updated and linked to destination" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/dl/dl.go:222 |  | ||||||
| msgid "Downloading source" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/dl/progress_tui.go:100 |  | ||||||
| msgid "%s: done!\n" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/dl/progress_tui.go:104 |  | ||||||
| msgid "%s %s downloading at %s/s\n" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/logger/log.go:41 | #: internal/logger/log.go:41 | ||||||
| msgid "ERROR" | msgid "ERROR" | ||||||
| msgstr "" | msgstr "" | ||||||
| @@ -465,6 +441,30 @@ msgstr "" | |||||||
| msgid "Error while running app" | msgid "Error while running app" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #: pkg/dl/dl.go:170 | ||||||
|  | msgid "Source can be updated, updating if required" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: pkg/dl/dl.go:201 | ||||||
|  | msgid "Source found in cache and linked to destination" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: pkg/dl/dl.go:208 | ||||||
|  | msgid "Source updated and linked to destination" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: pkg/dl/dl.go:222 | ||||||
|  | msgid "Downloading source" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: pkg/dl/progress_tui.go:100 | ||||||
|  | msgid "%s: done!\n" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: pkg/dl/progress_tui.go:104 | ||||||
|  | msgid "%s %s downloading at %s/s\n" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: refresh.go:30 | #: refresh.go:30 | ||||||
| msgid "Pull all repositories that have changed" | msgid "Pull all repositories that have changed" | ||||||
| msgstr "" | msgstr "" | ||||||
|   | |||||||
| @@ -369,30 +369,6 @@ msgid "" | |||||||
| msgstr "" | msgstr "" | ||||||
| "Версия базы данных не существует. Запустите alr fix, если что-то не работает." | "Версия базы данных не существует. Запустите alr fix, если что-то не работает." | ||||||
|  |  | ||||||
| #: internal/dl/dl.go:170 |  | ||||||
| msgid "Source can be updated, updating if required" |  | ||||||
| msgstr "Исходный код можно обновлять, обновляя при необходимости" |  | ||||||
|  |  | ||||||
| #: internal/dl/dl.go:201 |  | ||||||
| msgid "Source found in cache and linked to destination" |  | ||||||
| msgstr "Источник найден в кэше и связан с пунктом назначения" |  | ||||||
|  |  | ||||||
| #: internal/dl/dl.go:208 |  | ||||||
| msgid "Source updated and linked to destination" |  | ||||||
| msgstr "Источник обновлён и связан с пунктом назначения" |  | ||||||
|  |  | ||||||
| #: internal/dl/dl.go:222 |  | ||||||
| msgid "Downloading source" |  | ||||||
| msgstr "Скачивание источника" |  | ||||||
|  |  | ||||||
| #: internal/dl/progress_tui.go:100 |  | ||||||
| msgid "%s: done!\n" |  | ||||||
| msgstr "%s: выполнено!\n" |  | ||||||
|  |  | ||||||
| #: internal/dl/progress_tui.go:104 |  | ||||||
| msgid "%s %s downloading at %s/s\n" |  | ||||||
| msgstr "%s %s загружается — %s/с\n" |  | ||||||
|  |  | ||||||
| #: internal/logger/log.go:41 | #: internal/logger/log.go:41 | ||||||
| msgid "ERROR" | msgid "ERROR" | ||||||
| msgstr "ОШИБКА" | msgstr "ОШИБКА" | ||||||
| @@ -481,6 +457,30 @@ msgstr "Показать справку" | |||||||
| msgid "Error while running app" | msgid "Error while running app" | ||||||
| msgstr "Ошибка при запуске приложения" | msgstr "Ошибка при запуске приложения" | ||||||
|  |  | ||||||
|  | #: pkg/dl/dl.go:170 | ||||||
|  | msgid "Source can be updated, updating if required" | ||||||
|  | msgstr "Исходный код можно обновлять, обновляя при необходимости" | ||||||
|  |  | ||||||
|  | #: pkg/dl/dl.go:201 | ||||||
|  | msgid "Source found in cache and linked to destination" | ||||||
|  | msgstr "Источник найден в кэше и связан с пунктом назначения" | ||||||
|  |  | ||||||
|  | #: pkg/dl/dl.go:208 | ||||||
|  | msgid "Source updated and linked to destination" | ||||||
|  | msgstr "Источник обновлён и связан с пунктом назначения" | ||||||
|  |  | ||||||
|  | #: pkg/dl/dl.go:222 | ||||||
|  | msgid "Downloading source" | ||||||
|  | msgstr "Скачивание источника" | ||||||
|  |  | ||||||
|  | #: pkg/dl/progress_tui.go:100 | ||||||
|  | msgid "%s: done!\n" | ||||||
|  | msgstr "%s: выполнено!\n" | ||||||
|  |  | ||||||
|  | #: pkg/dl/progress_tui.go:104 | ||||||
|  | msgid "%s %s downloading at %s/s\n" | ||||||
|  | msgstr "%s %s загружается — %s/с\n" | ||||||
|  |  | ||||||
| #: refresh.go:30 | #: refresh.go:30 | ||||||
| msgid "Pull all repositories that have changed" | msgid "Pull all repositories that have changed" | ||||||
| msgstr "Скачать все изменённые репозитории" | msgstr "Скачать все изменённые репозитории" | ||||||
|   | |||||||
| @@ -55,7 +55,7 @@ var ( | |||||||
| 
 | 
 | ||||||
| // Массив доступных загрузчиков в порядке их проверки | // Массив доступных загрузчиков в порядке их проверки | ||||||
| var Downloaders = []Downloader{ | var Downloaders = []Downloader{ | ||||||
| 	GitDownloader{}, | 	&GitDownloader{}, | ||||||
| 	TorrentDownloader{}, | 	TorrentDownloader{}, | ||||||
| 	FileDownloader{}, | 	FileDownloader{}, | ||||||
| } | } | ||||||
| @@ -32,8 +32,8 @@ import ( | |||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| 
 | 
 | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/dl" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dl" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dlcache" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type TestALRConfig struct{} | type TestALRConfig struct{} | ||||||
| @@ -20,8 +20,11 @@ | |||||||
| package dl | package dl | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"bytes" | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"encoding/hex" | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	"log/slog" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"path" | 	"path" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| @@ -48,7 +51,7 @@ func (GitDownloader) MatchURL(u string) bool { | |||||||
| // Download uses git to clone the repository from the specified URL. | // Download uses git to clone the repository from the specified URL. | ||||||
| // It allows specifying the revision, depth and recursion options | // It allows specifying the revision, depth and recursion options | ||||||
| // via query string | // via query string | ||||||
| func (GitDownloader) Download(ctx context.Context, opts Options) (Type, string, error) { | func (d *GitDownloader) Download(ctx context.Context, opts Options) (Type, string, error) { | ||||||
| 	u, err := url.Parse(opts.URL) | 	u, err := url.Parse(opts.URL) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return 0, "", err | 		return 0, "", err | ||||||
| @@ -60,6 +63,9 @@ func (GitDownloader) Download(ctx context.Context, opts Options) (Type, string, | |||||||
| 	rev := query.Get("~rev") | 	rev := query.Get("~rev") | ||||||
| 	query.Del("~rev") | 	query.Del("~rev") | ||||||
| 
 | 
 | ||||||
|  | 	// Right now, this only affects the return value of name, | ||||||
|  | 	// which will be used by dl_cache. | ||||||
|  | 	// It seems wrong, but for now it's better to leave it as it is. | ||||||
| 	name := query.Get("~name") | 	name := query.Get("~name") | ||||||
| 	query.Del("~name") | 	query.Del("~name") | ||||||
| 
 | 
 | ||||||
| @@ -121,6 +127,11 @@ func (GitDownloader) Download(ctx context.Context, opts Options) (Type, string, | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	err = d.verifyHash(opts) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, "", err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if name == "" { | 	if name == "" { | ||||||
| 		name = strings.TrimSuffix(path.Base(u.Path), ".git") | 		name = strings.TrimSuffix(path.Base(u.Path), ".git") | ||||||
| 	} | 	} | ||||||
| @@ -128,12 +139,36 @@ func (GitDownloader) Download(ctx context.Context, opts Options) (Type, string, | |||||||
| 	return TypeDir, name, nil | 	return TypeDir, name, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (GitDownloader) verifyHash(opts Options) error { | ||||||
|  | 	if opts.Hash != nil { | ||||||
|  | 		h, err := opts.NewHash() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		err = HashDir(opts.Destination, h) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		sum := h.Sum(nil) | ||||||
|  | 
 | ||||||
|  | 		slog.Warn("validate checksum", "real", hex.EncodeToString(sum), "expected", hex.EncodeToString(opts.Hash)) | ||||||
|  | 
 | ||||||
|  | 		if !bytes.Equal(sum, opts.Hash) { | ||||||
|  | 			return ErrChecksumMismatch | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Update uses git to pull the repository and update it | // Update uses git to pull the repository and update it | ||||||
| // to the latest revision. It allows specifying the depth | // to the latest revision. It allows specifying the depth | ||||||
| // and recursion options via query string. It returns | // and recursion options via query string. It returns | ||||||
| // true if update was successful and false if the | // true if update was successful and false if the | ||||||
| // repository is already up-to-date | // repository is already up-to-date | ||||||
| func (GitDownloader) Update(opts Options) (bool, error) { | func (d *GitDownloader) Update(opts Options) (bool, error) { | ||||||
| 	u, err := url.Parse(opts.URL) | 	u, err := url.Parse(opts.URL) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return false, err | 		return false, err | ||||||
| @@ -183,18 +218,21 @@ func (GitDownloader) Update(opts Options) (bool, error) { | |||||||
| 	manifestOK := err == nil | 	manifestOK := err == nil | ||||||
| 
 | 
 | ||||||
| 	err = w.Pull(po) | 	err = w.Pull(po) | ||||||
|  | 	if err != nil { | ||||||
| 		if errors.Is(err, git.NoErrAlreadyUpToDate) { | 		if errors.Is(err, git.NoErrAlreadyUpToDate) { | ||||||
| 			return false, nil | 			return false, nil | ||||||
| 	} else if err != nil { | 		} | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	err = d.verifyHash(opts) | ||||||
|  | 	if err != nil { | ||||||
| 		return false, err | 		return false, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if manifestOK { | 	if manifestOK { | ||||||
| 		err = writeManifest(opts.Destination, m) | 		err = writeManifest(opts.Destination, m) | ||||||
| 		if err != nil { |  | ||||||
| 			return true, err |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return true, nil | 	return true, err | ||||||
| } | } | ||||||
							
								
								
									
										183
									
								
								pkg/dl/git_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								pkg/dl/git_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,183 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU 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 General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package dl_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"encoding/hex" | ||||||
|  | 	"os" | ||||||
|  | 	"os/exec" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dl" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestGitDownloaderMatchUrl(t *testing.T) { | ||||||
|  | 	d := dl.GitDownloader{} | ||||||
|  | 	assert.True(t, d.MatchURL("git+https://example.com/org/project.git")) | ||||||
|  | 	assert.False(t, d.MatchURL("https://example.com/org/project.git")) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestGitDownloaderDownload(t *testing.T) { | ||||||
|  | 	d := dl.GitDownloader{} | ||||||
|  |  | ||||||
|  | 	createTempDir := func(t *testing.T, name string) string { | ||||||
|  | 		t.Helper() | ||||||
|  | 		dir, err := os.MkdirTemp("", "test-"+name) | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		t.Cleanup(func() { | ||||||
|  | 			_ = os.RemoveAll(dir) | ||||||
|  | 		}) | ||||||
|  | 		return dir | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	t.Run("simple", func(t *testing.T) { | ||||||
|  | 		dest := createTempDir(t, "simple") | ||||||
|  |  | ||||||
|  | 		dlType, name, err := d.Download(context.Background(), dl.Options{ | ||||||
|  | 			URL:         "git+https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git", | ||||||
|  | 			Destination: dest, | ||||||
|  | 		}) | ||||||
|  |  | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.Equal(t, dl.TypeDir, dlType) | ||||||
|  | 		assert.Equal(t, "repo-for-tests", name) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("with hash", func(t *testing.T) { | ||||||
|  | 		dest := createTempDir(t, "with-hash") | ||||||
|  |  | ||||||
|  | 		hsh, err := hex.DecodeString("33c912b855352663550003ca6b948ae3df1f38e2c036f5a85775df5967e143bf") | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 		dlType, name, err := d.Download(context.Background(), dl.Options{ | ||||||
|  | 			URL:           "git+https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git?~rev=init&~name=test", | ||||||
|  | 			Destination:   dest, | ||||||
|  | 			Hash:          hsh, | ||||||
|  | 			HashAlgorithm: "sha256", | ||||||
|  | 		}) | ||||||
|  |  | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.Equal(t, dl.TypeDir, dlType) | ||||||
|  | 		assert.Equal(t, "test", name) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("with hash (checksum mismatch)", func(t *testing.T) { | ||||||
|  | 		dest := createTempDir(t, "with-hash-checksum-mismatch") | ||||||
|  |  | ||||||
|  | 		hsh, err := hex.DecodeString("33c912b855352663550003ca6b948ae3df1f38e2c036f5a85775df5967e143bf") | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 		_, _, err = d.Download(context.Background(), dl.Options{ | ||||||
|  | 			URL:           "git+https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git", | ||||||
|  | 			Destination:   dest, | ||||||
|  | 			Hash:          hsh, | ||||||
|  | 			HashAlgorithm: "sha256", | ||||||
|  | 		}) | ||||||
|  |  | ||||||
|  | 		assert.ErrorIs(t, err, dl.ErrChecksumMismatch) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestGitDownloaderUpdate(t *testing.T) { | ||||||
|  | 	d := dl.GitDownloader{} | ||||||
|  |  | ||||||
|  | 	createTempDir := func(t *testing.T, name string) string { | ||||||
|  | 		t.Helper() | ||||||
|  | 		dir, err := os.MkdirTemp("", "test-"+name) | ||||||
|  |  | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		t.Cleanup(func() { | ||||||
|  | 			_ = os.RemoveAll(dir) | ||||||
|  | 		}) | ||||||
|  | 		return dir | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	setupOldRepo := func(t *testing.T, dest string) { | ||||||
|  | 		t.Helper() | ||||||
|  |  | ||||||
|  | 		cmd := exec.Command("git", "clone", "https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git", dest) | ||||||
|  | 		err := cmd.Run() | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 		cmd = exec.Command("git", "-C", dest, "reset", "--hard", "init") | ||||||
|  | 		err = cmd.Run() | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	t.Run("simple", func(t *testing.T) { | ||||||
|  | 		dest := createTempDir(t, "update") | ||||||
|  |  | ||||||
|  | 		setupOldRepo(t, dest) | ||||||
|  |  | ||||||
|  | 		cmd := exec.Command("git", "-C", dest, "rev-parse", "HEAD") | ||||||
|  | 		oldHash, err := cmd.Output() | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 		updated, err := d.Update(dl.Options{ | ||||||
|  | 			URL:         "git+https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git", | ||||||
|  | 			Destination: dest, | ||||||
|  | 		}) | ||||||
|  |  | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.True(t, updated) | ||||||
|  |  | ||||||
|  | 		cmd = exec.Command("git", "-C", dest, "rev-parse", "HEAD") | ||||||
|  | 		newHash, err := cmd.Output() | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.NotEqual(t, string(oldHash), string(newHash), "Repository should be updated") | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("with hash", func(t *testing.T) { | ||||||
|  | 		dest := createTempDir(t, "update") | ||||||
|  |  | ||||||
|  | 		setupOldRepo(t, dest) | ||||||
|  |  | ||||||
|  | 		hsh, err := hex.DecodeString("0dc4f3c68c435d0cd7a5ee960f965815fa9c4ee0571839cdb8f9de56e06f91eb") | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 		updated, err := d.Update(dl.Options{ | ||||||
|  | 			URL:           "git+https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git~rev=test-update-git-downloader", | ||||||
|  | 			Destination:   dest, | ||||||
|  | 			Hash:          hsh, | ||||||
|  | 			HashAlgorithm: "sha256", | ||||||
|  | 		}) | ||||||
|  |  | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.True(t, updated) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("with hash (checksum mismatch)", func(t *testing.T) { | ||||||
|  | 		dest := createTempDir(t, "update") | ||||||
|  |  | ||||||
|  | 		setupOldRepo(t, dest) | ||||||
|  |  | ||||||
|  | 		hsh, err := hex.DecodeString("33c912b855352663550003ca6b948ae3df1f38e2c036f5a85775df5967e143bf") | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 		_, err = d.Update(dl.Options{ | ||||||
|  | 			URL:           "git+https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git?~rev=test-update-git-downloader", | ||||||
|  | 			Destination:   dest, | ||||||
|  | 			Hash:          hsh, | ||||||
|  | 			HashAlgorithm: "sha256", | ||||||
|  | 		}) | ||||||
|  |  | ||||||
|  | 		assert.ErrorIs(t, err, dl.ErrChecksumMismatch) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
							
								
								
									
										55
									
								
								pkg/dl/utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								pkg/dl/utils.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU 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 General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package dl | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"hash" | ||||||
|  | 	"io" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func HashDir(dirPath string, h hash.Hash) error { | ||||||
|  | 	err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error { | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		// Skip .git directory | ||||||
|  | 		if info.IsDir() && info.Name() == ".git" { | ||||||
|  | 			return filepath.SkipDir | ||||||
|  | 		} | ||||||
|  | 		// Skip directories (only process files) | ||||||
|  | 		if !info.Mode().IsRegular() { | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 		// Open file | ||||||
|  | 		f, err := os.Open(path) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		defer f.Close() | ||||||
|  | 		// Write file content to hasher | ||||||
|  | 		if _, err := io.Copy(h, f); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
| @@ -29,7 +29,7 @@ import ( | |||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dlcache" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type TestALRConfig struct { | type TestALRConfig struct { | ||||||
		Reference in New Issue
	
	Block a user