Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/go-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ jobs:
- name: Checkout
uses: actions/checkout@v4

- name: Install flatpak
run: sudo apt update && sudo apt install -y flatpak

- name: Add flathub
run: sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo

- name: Add a flatpak that mutagen could support
run: sudo flatpak install -y org.freedesktop.Platform/x86_64/24.08 app.zen_browser.zen

- name: Set up Go
uses: actions/setup-go@v5
with:
Expand Down
78 changes: 78 additions & 0 deletions core/internal/utils/flatpak.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package utils

import (
"bytes"
"errors"
"os/exec"
"strings"
)

func FlatpakInPath() bool {
_, err := exec.LookPath("flatpak")
return err == nil
}

func FlatpakExists(name string) bool {
if !FlatpakInPath() {
return false
}

cmd := exec.Command("flatpak", "info", name)
err := cmd.Run()
return err == nil
}

func FlatpakSearchBySubstring(substring string) bool {
if !FlatpakInPath() {
return false
}

cmd := exec.Command("flatpak", "list", "--app")
var stdout bytes.Buffer
cmd.Stdout = &stdout

if err := cmd.Run(); err != nil {
return false
}

out := stdout.String()

for line := range strings.SplitSeq(out, "\n") {
fields := strings.Fields(line)
if len(fields) > 1 {
id := fields[1]
idParts := strings.Split(id, ".")
// We are assuming that the last part of the ID is
// the package name we're looking for. This might
// not always be true, some developers use arbitrary
// suffixes.
if len(idParts) > 0 && idParts[len(idParts)-1] == substring {
cmd := exec.Command("flatpak", "info", id)
err := cmd.Run()
return err == nil
}
}
}
return false
}

func FlatpakInstallationDir(name string) (string, error) {
if !FlatpakInPath() {
return "", errors.New("flatpak not found in PATH")
}

cmd := exec.Command("flatpak", "info", "--show-location", name)
var stdout bytes.Buffer
cmd.Stdout = &stdout

if err := cmd.Run(); err != nil {
return "", errors.New("flatpak not installed: " + name)
}

location := strings.TrimSpace(stdout.String())
if location == "" {
return "", errors.New("installation directory not found for: " + name)
}

return location, nil
}
210 changes: 210 additions & 0 deletions core/internal/utils/flatpak_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
package utils

import (
"os"
"path/filepath"
"strings"
"testing"
)

func TestFlatpakInPathAvailable(t *testing.T) {
result := FlatpakInPath()
if !result {
t.Skip("flatpak not in PATH")
}
if !result {
t.Errorf("expected true when flatpak is in PATH")
}
}

func TestFlatpakInPathUnavailable(t *testing.T) {
tempDir := t.TempDir()
t.Setenv("PATH", tempDir)

result := FlatpakInPath()
if result {
t.Errorf("expected false when flatpak not in PATH, got true")
}
}

func TestFlatpakExistsValidPackage(t *testing.T) {
if !FlatpakInPath() {
t.Skip("flatpak not in PATH")
}

result := FlatpakExists("com.nonexistent.package.test")
if result {
t.Logf("package exists (unexpected but not an error)")
}
}

func TestFlatpakExistsNoFlatpak(t *testing.T) {
tempDir := t.TempDir()
t.Setenv("PATH", tempDir)

result := FlatpakExists("any.package.name")
if result {
t.Errorf("expected false when flatpak not in PATH, got true")
}
}

func TestFlatpakSearchBySubstringNoFlatpak(t *testing.T) {
tempDir := t.TempDir()
t.Setenv("PATH", tempDir)

result := FlatpakSearchBySubstring("test")
if result {
t.Errorf("expected false when flatpak not in PATH, got true")
}
}

func TestFlatpakSearchBySubstringNonexistent(t *testing.T) {
if !FlatpakInPath() {
t.Skip("flatpak not in PATH")
}

result := FlatpakSearchBySubstring("ThisIsAVeryUnlikelyPackageName12345")
if result {
t.Errorf("expected false for nonexistent package substring")
}
}

func TestFlatpakInstallationDirNoFlatpak(t *testing.T) {
tempDir := t.TempDir()
t.Setenv("PATH", tempDir)

_, err := FlatpakInstallationDir("any.package.name")
if err == nil {
t.Errorf("expected error when flatpak not in PATH")
}
if err != nil && !strings.Contains(err.Error(), "not found in PATH") {
t.Errorf("expected 'not found in PATH' error, got: %v", err)
}
}

func TestFlatpakInstallationDirNonexistent(t *testing.T) {
if !FlatpakInPath() {
t.Skip("flatpak not in PATH")
}

_, err := FlatpakInstallationDir("com.nonexistent.package.test")
if err == nil {
t.Errorf("expected error for nonexistent package")
}
if err != nil && !strings.Contains(err.Error(), "not installed") {
t.Errorf("expected 'not installed' error, got: %v", err)
}
}

func TestFlatpakInstallationDirValid(t *testing.T) {
if !FlatpakInPath() {
t.Skip("flatpak not in PATH")
}

// This test requires a known installed flatpak
// We can't guarantee any specific flatpak is installed,
// so we'll skip if we can't find a common one
commonFlatpaks := []string{
"org.mozilla.firefox",
"org.gnome.Calculator",
"org.freedesktop.Platform",
}

var testPackage string
for _, pkg := range commonFlatpaks {
if FlatpakExists(pkg) {
testPackage = pkg
break
}
}

if testPackage == "" {
t.Skip("no common flatpak packages found for testing")
}

result, err := FlatpakInstallationDir(testPackage)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result == "" {
t.Errorf("expected non-empty installation directory")
}
if !strings.Contains(result, testPackage) {
t.Logf("installation directory %s doesn't contain package name (may be expected)", result)
}
}

func TestFlatpakExistsCommandFailure(t *testing.T) {
if !FlatpakInPath() {
t.Skip("flatpak not in PATH")
}

// Mock a failing flatpak command through PATH interception
tempDir := t.TempDir()
fakeFlatpak := filepath.Join(tempDir, "flatpak")

script := "#!/bin/sh\nexit 1\n"
err := os.WriteFile(fakeFlatpak, []byte(script), 0755)
if err != nil {
t.Fatalf("failed to create fake flatpak: %v", err)
}

originalPath := os.Getenv("PATH")
t.Setenv("PATH", tempDir+":"+originalPath)

result := FlatpakExists("test.package")
if result {
t.Errorf("expected false when flatpak command fails, got true")
}
}

func TestFlatpakSearchBySubstringCommandFailure(t *testing.T) {
if !FlatpakInPath() {
t.Skip("flatpak not in PATH")
}

// Mock a failing flatpak command through PATH interception
tempDir := t.TempDir()
fakeFlatpak := filepath.Join(tempDir, "flatpak")

script := "#!/bin/sh\nexit 1\n"
err := os.WriteFile(fakeFlatpak, []byte(script), 0755)
if err != nil {
t.Fatalf("failed to create fake flatpak: %v", err)
}

originalPath := os.Getenv("PATH")
t.Setenv("PATH", tempDir+":"+originalPath)

result := FlatpakSearchBySubstring("test")
if result {
t.Errorf("expected false when flatpak command fails, got true")
}
}

func TestFlatpakInstallationDirCommandFailure(t *testing.T) {
if !FlatpakInPath() {
t.Skip("flatpak not in PATH")
}

// Mock a failing flatpak command through PATH interception
tempDir := t.TempDir()
fakeFlatpak := filepath.Join(tempDir, "flatpak")

script := "#!/bin/sh\nexit 1\n"
err := os.WriteFile(fakeFlatpak, []byte(script), 0755)
if err != nil {
t.Fatalf("failed to create fake flatpak: %v", err)
}

originalPath := os.Getenv("PATH")
t.Setenv("PATH", tempDir+":"+originalPath)

_, err = FlatpakInstallationDir("test.package")
if err == nil {
t.Errorf("expected error when flatpak command fails")
}
if err != nil && !strings.Contains(err.Error(), "not installed") {
t.Errorf("expected 'not installed' error, got: %v", err)
}
}
Loading