Files
QuickandClaude Fable 5 e3a209c4ff fix(plugins): package binary installs as zip archives and self-repair legacy raw-binary rows
Binary catalog install/update paths (installBinary/replaceBinary) saved raw
plugin binaries into plugin_archives.archive_bytes, but startup preload opens
that column with zip.NewReader expecting a package containing manifest.json
and the plugin binary. Affected installs failed preload after restart with
"zip: not a valid zip file".

- Package manifest.json + binary into a validated zip before SaveArchive on
  both binary install paths.
- Self-repair legacy rows in ArchiveCache.Ensure: when stored bytes are not a
  zip but hash-match both the stored checksum and the stored manifest's
  checksum, repackage and persist the repaired archive. A persist failure is
  logged, not fatal — the in-memory archive is already valid and recovery
  retries on the next preload.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 08:13:50 -04:00

151 lines
4.4 KiB
Go

package plugins
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"os"
"path/filepath"
"testing"
"google.golang.org/protobuf/encoding/protojson"
)
func TestArchiveCacheEnsureRecoversLegacyBinaryArchive(t *testing.T) {
ctx := context.Background()
binaryData := []byte("#!/bin/sh\nexit 0\n")
checksum := sha256.Sum256(binaryData)
manifest := testPluginManifest(t, "silo.metadb", "0.0.19")
manifest.Checksum = hex.EncodeToString(checksum[:])
manifestBytes, err := protojson.Marshal(manifest)
if err != nil {
t.Fatalf("protojson.Marshal() returned error: %v", err)
}
store := &legacyArchiveStore{
archive: &InstallationArchive{
InstallationID: 42,
ManifestJSON: manifestBytes,
Checksum: manifest.GetChecksum(),
Bytes: binaryData,
},
}
installDir := filepath.Join(t.TempDir(), "plugins", "silo.metadb", "0.0.19")
installation := &Installation{
ID: 42,
PluginID: manifest.GetPluginId(),
Version: manifest.GetVersion(),
InstallPath: filepath.Join(installDir, "plugin"),
}
got, err := NewArchiveCache(store).Ensure(ctx, installation)
if err != nil {
t.Fatalf("Ensure() returned error: %v", err)
}
if got.GetPluginId() != manifest.GetPluginId() {
t.Fatalf("manifest plugin_id = %q, want %q", got.GetPluginId(), manifest.GetPluginId())
}
if store.savedInstallationID != installation.ID {
t.Fatalf("repaired archive installation id = %d, want %d", store.savedInstallationID, installation.ID)
}
if !bytes.Equal(store.savedManifestJSON, manifestBytes) {
t.Fatal("repaired archive manifest does not match stored manifest")
}
if _, _, savedManifest, err := openPluginArchive(store.savedArchiveBytes); err != nil {
t.Fatalf("repaired archive is not a valid plugin archive: %v", err)
} else if savedManifest.GetChecksum() != manifest.GetChecksum() {
t.Fatalf("repaired archive checksum = %q, want %q", savedManifest.GetChecksum(), manifest.GetChecksum())
}
installedBinary, err := os.ReadFile(installation.InstallPath)
if err != nil {
t.Fatalf("read rehydrated plugin binary: %v", err)
}
if !bytes.Equal(installedBinary, binaryData) {
t.Fatal("rehydrated plugin binary does not match legacy binary bytes")
}
if _, err := os.Stat(InstalledManifestPath(installation.InstallPath)); err != nil {
t.Fatalf("expected rehydrated manifest file: %v", err)
}
}
func TestArchiveCacheEnsureRecoveryToleratesPersistFailure(t *testing.T) {
ctx := context.Background()
binaryData := []byte("#!/bin/sh\nexit 0\n")
checksum := sha256.Sum256(binaryData)
manifest := testPluginManifest(t, "silo.metadb", "0.0.19")
manifest.Checksum = hex.EncodeToString(checksum[:])
manifestBytes, err := protojson.Marshal(manifest)
if err != nil {
t.Fatalf("protojson.Marshal() returned error: %v", err)
}
store := &legacyArchiveStore{
archive: &InstallationArchive{
InstallationID: 42,
ManifestJSON: manifestBytes,
Checksum: manifest.GetChecksum(),
Bytes: binaryData,
},
saveErr: errors.New("db unavailable"),
}
installDir := filepath.Join(t.TempDir(), "plugins", "silo.metadb", "0.0.19")
installation := &Installation{
ID: 42,
PluginID: manifest.GetPluginId(),
Version: manifest.GetVersion(),
InstallPath: filepath.Join(installDir, "plugin"),
}
if _, err := NewArchiveCache(store).Ensure(ctx, installation); err != nil {
t.Fatalf("Ensure() returned error despite in-memory recovery: %v", err)
}
installedBinary, err := os.ReadFile(installation.InstallPath)
if err != nil {
t.Fatalf("read rehydrated plugin binary: %v", err)
}
if !bytes.Equal(installedBinary, binaryData) {
t.Fatal("rehydrated plugin binary does not match legacy binary bytes")
}
}
type legacyArchiveStore struct {
archive *InstallationArchive
saveErr error
savedInstallationID int
savedManifestJSON []byte
savedChecksum string
savedArchiveBytes []byte
}
func (s *legacyArchiveStore) GetArchive(context.Context, int) (*InstallationArchive, error) {
return s.archive, nil
}
func (s *legacyArchiveStore) SaveArchive(
_ context.Context,
installationID int,
manifestJSON []byte,
checksum string,
archiveBytes []byte,
) error {
if s.saveErr != nil {
return s.saveErr
}
s.savedInstallationID = installationID
s.savedManifestJSON = append([]byte(nil), manifestJSON...)
s.savedChecksum = checksum
s.savedArchiveBytes = append([]byte(nil), archiveBytes...)
return nil
}