2026-03-31 16:23:37 +02:00
|
|
|
package api_test
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/http/httptest"
|
2026-07-23 18:38:22 +02:00
|
|
|
"strings"
|
2026-03-31 16:23:37 +02:00
|
|
|
"testing"
|
|
|
|
|
|
2026-08-28 06:54:32 +02:00
|
|
|
"github.com/J3vb/OwnCord/Server/api"
|
|
|
|
|
"github.com/J3vb/OwnCord/Server/updater"
|
2026-03-31 16:23:37 +02:00
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// fakeGitHubRelease returns a test HTTP server that mimics the GitHub
|
2026-07-23 18:38:22 +02:00
|
|
|
// Releases API, serving a release with the given tag and the client update
|
|
|
|
|
// assets for every platform the release workflow publishes.
|
2026-03-31 16:23:37 +02:00
|
|
|
// Asset download URLs point back to the test server so FetchTextAsset works.
|
|
|
|
|
func fakeGitHubRelease(t *testing.T, tag string) *httptest.Server {
|
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
|
|
var srv *httptest.Server
|
|
|
|
|
mux := http.NewServeMux()
|
|
|
|
|
|
2026-07-23 18:38:22 +02:00
|
|
|
assetNames := []string{
|
|
|
|
|
"OwnCord_1.0.0_x64-setup.nsis.zip",
|
|
|
|
|
"OwnCord_1.0.0_x64-setup.nsis.zip.sig",
|
|
|
|
|
"OwnCord_1.0.0_amd64.AppImage.tar.gz",
|
|
|
|
|
"OwnCord_1.0.0_amd64.AppImage.tar.gz.sig",
|
|
|
|
|
"OwnCord_1.0.0_aarch64.AppImage.tar.gz",
|
|
|
|
|
"OwnCord_1.0.0_aarch64.AppImage.tar.gz.sig",
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 16:23:37 +02:00
|
|
|
mux.HandleFunc("/repos/test/repo/releases/latest", func(w http.ResponseWriter, r *http.Request) {
|
2026-07-23 18:38:22 +02:00
|
|
|
assets := make([]map[string]any, 0, len(assetNames))
|
|
|
|
|
for _, name := range assetNames {
|
|
|
|
|
assets = append(assets, map[string]any{
|
|
|
|
|
"name": name,
|
|
|
|
|
"browser_download_url": srv.URL + "/download/" + name,
|
|
|
|
|
})
|
|
|
|
|
}
|
2026-03-31 16:23:37 +02:00
|
|
|
resp := map[string]any{
|
|
|
|
|
"tag_name": tag,
|
|
|
|
|
"body": "Release notes here",
|
|
|
|
|
"html_url": "https://github.com/test/repo/releases/" + tag,
|
2026-07-23 18:38:22 +02:00
|
|
|
"assets": assets,
|
2026-03-31 16:23:37 +02:00
|
|
|
}
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
_ = json.NewEncoder(w).Encode(resp)
|
|
|
|
|
})
|
|
|
|
|
|
2026-07-23 18:38:22 +02:00
|
|
|
// Serve signature file content for any .sig asset.
|
|
|
|
|
mux.HandleFunc("/download/", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
if strings.HasSuffix(r.URL.Path, ".sig") {
|
|
|
|
|
_, _ = w.Write([]byte("dW50cnVzdGVkIGNvbW1lbnQ="))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
http.NotFound(w, r)
|
2026-03-31 16:23:37 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
srv = httptest.NewServer(mux)
|
|
|
|
|
t.Cleanup(srv.Close)
|
|
|
|
|
return srv
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-23 18:38:22 +02:00
|
|
|
// platformEntry decodes the response body and returns the platforms entry for
|
|
|
|
|
// the given target, failing the test if it is missing.
|
|
|
|
|
func platformEntry(t *testing.T, rr *httptest.ResponseRecorder, target string) map[string]any {
|
|
|
|
|
t.Helper()
|
|
|
|
|
var resp map[string]any
|
|
|
|
|
if err := json.NewDecoder(rr.Body).Decode(&resp); err != nil {
|
|
|
|
|
t.Fatalf("decode: %v", err)
|
|
|
|
|
}
|
|
|
|
|
platforms, ok := resp["platforms"].(map[string]any)
|
|
|
|
|
if !ok {
|
|
|
|
|
t.Fatalf("response missing platforms map: %v", resp)
|
|
|
|
|
}
|
|
|
|
|
entry, ok := platforms[target].(map[string]any)
|
|
|
|
|
if !ok {
|
|
|
|
|
t.Fatalf("platforms missing key %q: %v", target, platforms)
|
|
|
|
|
}
|
|
|
|
|
return entry
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 16:23:37 +02:00
|
|
|
func buildClientUpdateRouter(u *updater.Updater) http.Handler {
|
|
|
|
|
r := chi.NewRouter()
|
|
|
|
|
api.MountClientUpdateRoute(r, u)
|
|
|
|
|
return r
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClientUpdate_NewVersionAvailable(t *testing.T) {
|
|
|
|
|
srv := fakeGitHubRelease(t, "v2.0.0")
|
|
|
|
|
u := updater.NewUpdater("1.0.0", "", "test", "repo")
|
|
|
|
|
u.SetBaseURL(srv.URL)
|
|
|
|
|
|
|
|
|
|
router := buildClientUpdateRouter(u)
|
|
|
|
|
|
2026-07-23 18:38:22 +02:00
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/client-update/windows-x86_64-nsis/1.0.0", nil)
|
2026-03-31 16:23:37 +02:00
|
|
|
rr := httptest.NewRecorder()
|
|
|
|
|
router.ServeHTTP(rr, req)
|
|
|
|
|
|
|
|
|
|
if rr.Code != http.StatusOK {
|
|
|
|
|
t.Fatalf("status = %d, want 200; body: %s", rr.Code, rr.Body.String())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var resp map[string]any
|
|
|
|
|
if err := json.NewDecoder(rr.Body).Decode(&resp); err != nil {
|
|
|
|
|
t.Fatalf("decode: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if resp["version"] == nil {
|
|
|
|
|
t.Error("response missing 'version' field")
|
|
|
|
|
}
|
|
|
|
|
if resp["platforms"] == nil {
|
|
|
|
|
t.Error("response missing 'platforms' field")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClientUpdate_AlreadyLatest(t *testing.T) {
|
|
|
|
|
srv := fakeGitHubRelease(t, "v1.0.0")
|
|
|
|
|
u := updater.NewUpdater("1.0.0", "", "test", "repo")
|
|
|
|
|
u.SetBaseURL(srv.URL)
|
|
|
|
|
|
|
|
|
|
router := buildClientUpdateRouter(u)
|
|
|
|
|
|
2026-07-23 18:38:22 +02:00
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/client-update/windows-x86_64-nsis/1.0.0", nil)
|
2026-03-31 16:23:37 +02:00
|
|
|
rr := httptest.NewRecorder()
|
|
|
|
|
router.ServeHTTP(rr, req)
|
|
|
|
|
|
|
|
|
|
if rr.Code != http.StatusNoContent {
|
|
|
|
|
t.Errorf("status = %d, want 204; body: %s", rr.Code, rr.Body.String())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClientUpdate_FutureVersion(t *testing.T) {
|
|
|
|
|
srv := fakeGitHubRelease(t, "v1.0.0")
|
|
|
|
|
u := updater.NewUpdater("1.0.0", "", "test", "repo")
|
|
|
|
|
u.SetBaseURL(srv.URL)
|
|
|
|
|
|
|
|
|
|
router := buildClientUpdateRouter(u)
|
|
|
|
|
|
|
|
|
|
// Client has a newer version than the release.
|
2026-07-23 18:38:22 +02:00
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/client-update/windows-x86_64-nsis/2.0.0", nil)
|
|
|
|
|
rr := httptest.NewRecorder()
|
|
|
|
|
router.ServeHTTP(rr, req)
|
|
|
|
|
|
|
|
|
|
if rr.Code != http.StatusNoContent {
|
|
|
|
|
t.Errorf("status = %d, want 204; body: %s", rr.Code, rr.Body.String())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClientUpdate_WindowsTargetGetsNSISInstaller(t *testing.T) {
|
|
|
|
|
srv := fakeGitHubRelease(t, "v2.0.0")
|
|
|
|
|
u := updater.NewUpdater("1.0.0", "", "test", "repo")
|
|
|
|
|
u.SetBaseURL(srv.URL)
|
|
|
|
|
|
|
|
|
|
router := buildClientUpdateRouter(u)
|
|
|
|
|
|
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/client-update/windows-x86_64-nsis/1.0.0", nil)
|
|
|
|
|
rr := httptest.NewRecorder()
|
|
|
|
|
router.ServeHTTP(rr, req)
|
|
|
|
|
|
|
|
|
|
if rr.Code != http.StatusOK {
|
|
|
|
|
t.Fatalf("status = %d, want 200; body: %s", rr.Code, rr.Body.String())
|
|
|
|
|
}
|
|
|
|
|
entry := platformEntry(t, rr, "windows-x86_64-nsis")
|
|
|
|
|
url, _ := entry["url"].(string)
|
|
|
|
|
if !strings.HasSuffix(url, "_x64-setup.nsis.zip") {
|
|
|
|
|
t.Errorf("windows url = %q, want NSIS installer", url)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClientUpdate_LinuxTargetGetsAppImage(t *testing.T) {
|
|
|
|
|
srv := fakeGitHubRelease(t, "v2.0.0")
|
|
|
|
|
u := updater.NewUpdater("1.0.0", "", "test", "repo")
|
|
|
|
|
u.SetBaseURL(srv.URL)
|
|
|
|
|
|
|
|
|
|
router := buildClientUpdateRouter(u)
|
|
|
|
|
|
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/client-update/linux-x86_64-appimage/1.0.0", nil)
|
|
|
|
|
rr := httptest.NewRecorder()
|
|
|
|
|
router.ServeHTTP(rr, req)
|
|
|
|
|
|
|
|
|
|
if rr.Code != http.StatusOK {
|
|
|
|
|
t.Fatalf("status = %d, want 200; body: %s", rr.Code, rr.Body.String())
|
|
|
|
|
}
|
|
|
|
|
entry := platformEntry(t, rr, "linux-x86_64-appimage")
|
|
|
|
|
url, _ := entry["url"].(string)
|
|
|
|
|
if !strings.HasSuffix(url, "_amd64.AppImage.tar.gz") {
|
|
|
|
|
t.Errorf("linux url = %q, want x86_64 AppImage updater archive", url)
|
|
|
|
|
}
|
|
|
|
|
if sig, _ := entry["signature"].(string); sig == "" {
|
|
|
|
|
t.Error("linux platform entry missing signature")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClientUpdate_LinuxArm64TargetGetsAarch64AppImage(t *testing.T) {
|
|
|
|
|
srv := fakeGitHubRelease(t, "v2.0.0")
|
|
|
|
|
u := updater.NewUpdater("1.0.0", "", "test", "repo")
|
|
|
|
|
u.SetBaseURL(srv.URL)
|
|
|
|
|
|
|
|
|
|
router := buildClientUpdateRouter(u)
|
|
|
|
|
|
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/client-update/linux-aarch64-appimage/1.0.0", nil)
|
|
|
|
|
rr := httptest.NewRecorder()
|
|
|
|
|
router.ServeHTTP(rr, req)
|
|
|
|
|
|
|
|
|
|
if rr.Code != http.StatusOK {
|
|
|
|
|
t.Fatalf("status = %d, want 200; body: %s", rr.Code, rr.Body.String())
|
|
|
|
|
}
|
|
|
|
|
entry := platformEntry(t, rr, "linux-aarch64-appimage")
|
|
|
|
|
url, _ := entry["url"].(string)
|
|
|
|
|
if !strings.HasSuffix(url, "_aarch64.AppImage.tar.gz") {
|
|
|
|
|
t.Errorf("linux arm64 url = %q, want aarch64 AppImage updater archive", url)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClientUpdate_UnsupportedTargetNoContent(t *testing.T) {
|
|
|
|
|
srv := fakeGitHubRelease(t, "v2.0.0")
|
|
|
|
|
u := updater.NewUpdater("1.0.0", "", "test", "repo")
|
|
|
|
|
u.SetBaseURL(srv.URL)
|
|
|
|
|
|
|
|
|
|
router := buildClientUpdateRouter(u)
|
|
|
|
|
|
|
|
|
|
// No darwin client is published; the updater must not be offered a
|
|
|
|
|
// Windows installer for it.
|
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/client-update/darwin-aarch64-app/1.0.0", nil)
|
|
|
|
|
rr := httptest.NewRecorder()
|
|
|
|
|
router.ServeHTTP(rr, req)
|
|
|
|
|
|
|
|
|
|
if rr.Code != http.StatusNoContent {
|
|
|
|
|
t.Errorf("status = %d, want 204; body: %s", rr.Code, rr.Body.String())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClientUpdate_DebTargetNoContent(t *testing.T) {
|
|
|
|
|
srv := fakeGitHubRelease(t, "v2.0.0")
|
|
|
|
|
u := updater.NewUpdater("1.0.0", "", "test", "repo")
|
|
|
|
|
u.SetBaseURL(srv.URL)
|
|
|
|
|
|
|
|
|
|
router := buildClientUpdateRouter(u)
|
|
|
|
|
|
|
|
|
|
// The release ships .deb packages but no signed deb UPDATER artifact.
|
|
|
|
|
// A deb client falling back to the AppImage archive would fail install
|
|
|
|
|
// forever (the plugin's install_deb rejects gzip bytes), so it must get
|
|
|
|
|
// 204 rather than an artifact for a different installer.
|
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/client-update/linux-x86_64-deb/1.0.0", nil)
|
2026-03-31 16:23:37 +02:00
|
|
|
rr := httptest.NewRecorder()
|
|
|
|
|
router.ServeHTTP(rr, req)
|
|
|
|
|
|
|
|
|
|
if rr.Code != http.StatusNoContent {
|
|
|
|
|
t.Errorf("status = %d, want 204; body: %s", rr.Code, rr.Body.String())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClientUpdate_GitHubError(t *testing.T) {
|
|
|
|
|
// Server that always returns 500.
|
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
|
}))
|
|
|
|
|
t.Cleanup(srv.Close)
|
|
|
|
|
|
|
|
|
|
u := updater.NewUpdater("1.0.0", "", "test", "repo")
|
|
|
|
|
u.SetBaseURL(srv.URL)
|
|
|
|
|
|
|
|
|
|
router := buildClientUpdateRouter(u)
|
|
|
|
|
|
2026-07-23 18:38:22 +02:00
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/client-update/windows-x86_64-nsis/1.0.0", nil)
|
2026-03-31 16:23:37 +02:00
|
|
|
rr := httptest.NewRecorder()
|
|
|
|
|
router.ServeHTTP(rr, req)
|
|
|
|
|
|
|
|
|
|
if rr.Code != http.StatusBadGateway {
|
|
|
|
|
t.Errorf("status = %d, want 502; body: %s", rr.Code, rr.Body.String())
|
|
|
|
|
}
|
|
|
|
|
}
|