128 lines
3.4 KiB
Go
128 lines
3.4 KiB
Go
//go:build (windows || darwin) && updater_live
|
|
|
|
package updater
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ollama/ollama/app/store"
|
|
"github.com/ollama/ollama/app/version"
|
|
)
|
|
|
|
// TestLiveAppUpdate exercises the production update endpoint and downloads the
|
|
// current OS update artifact. It is intentionally excluded from normal test
|
|
// runs because it depends on ollama.com and downloads a release artifact.
|
|
//
|
|
// Run with:
|
|
//
|
|
// go test -tags updater_live -run TestLiveAppUpdate ./app/updater
|
|
func TestLiveAppUpdate(t *testing.T) {
|
|
const spoofedVersion = "0.20.0"
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
|
defer cancel()
|
|
|
|
oldUpdateStageDir := UpdateStageDir
|
|
oldUpdateDownloaded := UpdateDownloaded
|
|
oldVerifyDownload := VerifyDownload
|
|
oldVersion := version.Version
|
|
defer func() {
|
|
UpdateStageDir = oldUpdateStageDir
|
|
UpdateDownloaded = oldUpdateDownloaded
|
|
VerifyDownload = oldVerifyDownload
|
|
version.Version = oldVersion
|
|
}()
|
|
|
|
version.Version = spoofedVersion
|
|
|
|
expectedFilename := ""
|
|
switch runtime.GOOS {
|
|
case "windows":
|
|
t.Setenv("LOCALAPPDATA", t.TempDir())
|
|
expectedFilename = "OllamaSetup.exe"
|
|
case "darwin":
|
|
expectedFilename = "Ollama-darwin.zip"
|
|
default:
|
|
t.Fatalf("unsupported updater live test OS %q", runtime.GOOS)
|
|
}
|
|
|
|
UpdateStageDir = filepath.Join(t.TempDir(), "updates")
|
|
UpdateDownloaded = false
|
|
verifyCalled := false
|
|
VerifyDownload = func() error {
|
|
verifyCalled = true
|
|
return verifyDownload()
|
|
}
|
|
|
|
updater := &Updater{Store: &store.Store{DBPath: filepath.Join(t.TempDir(), "db.sqlite")}}
|
|
defer updater.Store.Close()
|
|
|
|
available, updateResp := updater.checkForUpdate(ctx)
|
|
if !available {
|
|
t.Fatalf("expected production update check to offer an update for spoofed version %s", spoofedVersion)
|
|
}
|
|
if updateResp.UpdateURL == "" {
|
|
t.Fatal("production update response did not include a download URL")
|
|
}
|
|
t.Logf("production update version=%q url=%q", updateResp.UpdateVersion, updateResp.UpdateURL)
|
|
|
|
if err := updater.DownloadNewRelease(ctx, updateResp); err != nil {
|
|
t.Fatalf("download production update: %v", err)
|
|
}
|
|
|
|
staged := getStagedUpdate()
|
|
if staged == "" {
|
|
t.Fatal("production update was not staged")
|
|
}
|
|
t.Logf("staged production update at %s", staged)
|
|
|
|
assertPathInsideDir(t, UpdateStageDir, staged)
|
|
if filepath.Base(staged) != expectedFilename {
|
|
t.Fatalf("expected staged %s update filename to be %q, got %q", runtime.GOOS, expectedFilename, filepath.Base(staged))
|
|
}
|
|
expectedExt := filepath.Ext(expectedFilename)
|
|
if filepath.Ext(staged) != expectedExt {
|
|
t.Fatalf("expected staged %s update to be a %s artifact, got %s", runtime.GOOS, expectedExt, staged)
|
|
}
|
|
|
|
info, err := os.Stat(staged)
|
|
if err != nil {
|
|
t.Fatalf("stat staged update: %v", err)
|
|
}
|
|
if info.Size() == 0 {
|
|
t.Fatal("staged production update is empty")
|
|
}
|
|
|
|
if !verifyCalled {
|
|
t.Fatal("DownloadNewRelease did not call VerifyDownload")
|
|
}
|
|
t.Logf("production updater download path verified staged %s update", runtime.GOOS)
|
|
}
|
|
|
|
func assertPathInsideDir(t *testing.T, dir, name string) {
|
|
t.Helper()
|
|
|
|
dir, err := filepath.Abs(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
name, err = filepath.Abs(name)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rel, err := filepath.Rel(dir, name)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) || filepath.IsAbs(rel) {
|
|
t.Fatalf("staged update escaped update stage dir: %s", name)
|
|
}
|
|
}
|