This commit is contained in:
delete 2026-06-10 20:51:17 +03:00
commit b15b95781c
108 changed files with 14802 additions and 0 deletions

View file

@ -0,0 +1,323 @@
package httpserver_test
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"mime/multipart"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strings"
"testing"
)
func TestAdminProjectTagAndReleaseFlow(t *testing.T) {
t.Parallel()
router, cfg, store := newTestRouterWithStore(t)
sessionCookie := loginAsAdmin(t, router)
projectLocation := submitForm(t, router, http.MethodPost, "/admin/projects", url.Values{
"name": {"Desktop App"},
"slug": {""},
"description": {"Primary desktop updater stream."},
}, http.StatusSeeOther, sessionCookie)
projectID := extractResourceID(t, projectLocation, "/admin/projects/")
submitForm(t, router, http.MethodPost, "/admin/projects/"+projectID, url.Values{
"name": {"Desktop App"},
"slug": {"desktop-app"},
"description": {"Primary desktop updater stream with release uploads."},
}, http.StatusSeeOther, sessionCookie)
tagLocation := submitForm(t, router, http.MethodPost, "/admin/tags", url.Values{
"name": {"Windows Stable"},
"slug": {""},
"description": {"Windows production releases."},
}, http.StatusSeeOther, sessionCookie)
tagID := extractResourceID(t, tagLocation, "/admin/tags/")
submitForm(t, router, http.MethodPost, "/admin/projects/"+projectID+"/tags", url.Values{
"tag_id": {tagID},
}, http.StatusSeeOther, sessionCookie)
artifactBody := []byte("release-payload-1.0.0")
uploadLocation := submitMultipartForm(t, router, "/admin/projects/"+projectID+"/releases", map[string]string{
"version": "1.0.0",
"build": "build-42",
"release_notes": "Initial Windows stable rollout.",
}, "artifact", "..\\Desktop App 1.0.0.zip", artifactBody, sessionCookie)
if !strings.Contains(uploadLocation, "status=release-uploaded") {
t.Fatalf("expected release upload redirect status, got %q", uploadLocation)
}
projectIDInt := mustParseInt64(t, projectID)
project, err := store.Projects.GetByID(context.Background(), projectIDInt)
if err != nil {
t.Fatalf("load project: %v", err)
}
if project.Slug != "desktop-app" {
t.Fatalf("expected auto-generated project slug to persist, got %q", project.Slug)
}
if !project.IsActive {
t.Fatal("expected project to remain active until the archive action runs")
}
projectTags, err := store.Projects.ListTags(context.Background(), project.ID)
if err != nil {
t.Fatalf("load project tags: %v", err)
}
if len(projectTags) != 1 || projectTags[0].Slug != "windows-stable" {
t.Fatalf("expected one attached tag, got %+v", projectTags)
}
releases, err := store.Releases.ListByProjectID(context.Background(), project.ID)
if err != nil {
t.Fatalf("load releases: %v", err)
}
if len(releases) != 1 {
t.Fatalf("expected one release, got %d", len(releases))
}
release := releases[0].Release
if release.Filename != "desktop-app-1.0.0.zip" {
t.Fatalf("expected sanitized filename, got %q", release.Filename)
}
expectedChecksum := sha256.Sum256(artifactBody)
if release.ChecksumSHA256 != hex.EncodeToString(expectedChecksum[:]) {
t.Fatalf("expected checksum %s, got %s", hex.EncodeToString(expectedChecksum[:]), release.ChecksumSHA256)
}
if release.StoragePath != "desktop-app/1.0.0/build-42/desktop-app-1.0.0.zip" {
t.Fatalf("unexpected storage path %q", release.StoragePath)
}
artifactPath := filepath.Join(cfg.ArtifactsDir, filepath.FromSlash(release.StoragePath))
if strings.HasPrefix(artifactPath, cfg.StaticDir) {
t.Fatalf("artifact path %q should not be inside static dir %q", artifactPath, cfg.StaticDir)
}
storedArtifact, err := os.ReadFile(artifactPath)
if err != nil {
t.Fatalf("read artifact from disk: %v", err)
}
if !bytes.Equal(storedArtifact, artifactBody) {
t.Fatal("stored artifact body did not match uploaded payload")
}
recorder := performRequest(t, router, http.MethodGet, "/admin/projects/"+projectID, nil, sessionCookie)
if recorder.Code != http.StatusOK {
t.Fatalf("expected project detail page to load, got %d", recorder.Code)
}
projectBody := recorder.Body.String()
if !strings.Contains(projectBody, "/api/v1/projects/desktop-app/releases/latest") {
t.Fatal("expected project detail page to show latest release metadata endpoint")
}
if !strings.Contains(projectBody, "/api/v1/releases/1/download") {
t.Fatal("expected project detail page to show release download endpoint")
}
submitForm(t, router, http.MethodPost, "/admin/projects/"+projectID+"/archive", url.Values{
"state": {"archive"},
}, http.StatusSeeOther, sessionCookie)
project, err = store.Projects.GetByID(context.Background(), projectIDInt)
if err != nil {
t.Fatalf("reload project after archive: %v", err)
}
if project.IsActive {
t.Fatal("expected archived project to be inactive")
}
recorder = performRequest(t, router, http.MethodGet, "/admin/projects", nil, sessionCookie)
if recorder.Code != http.StatusOK {
t.Fatalf("expected projects page to load, got %d", recorder.Code)
}
if !strings.Contains(recorder.Body.String(), "Desktop App") {
t.Fatal("expected projects page to list the created project")
}
recorder = performRequest(t, router, http.MethodGet, "/admin/tags", nil, sessionCookie)
if recorder.Code != http.StatusOK {
t.Fatalf("expected tags page to load, got %d", recorder.Code)
}
if !strings.Contains(recorder.Body.String(), "Windows Stable") {
t.Fatal("expected tags page to list the created tag")
}
}
func loginAsAdmin(t *testing.T, router http.Handler) *http.Cookie {
t.Helper()
csrfCookie := ensureCSRFCookie(t, router)
recorder := performRequest(t, router, http.MethodPost, "/admin/login", url.Values{
"email": {"admin@example.com"},
"password": {"correct horse battery staple"},
"next": {"/admin"},
"csrf_token": {csrfCookie.Value},
}, csrfCookie)
if recorder.Code != http.StatusSeeOther {
t.Fatalf("expected login redirect, got %d", recorder.Code)
}
for _, cookie := range recorder.Result().Cookies() {
if cookie.Name == "update_server_session" {
return cookie
}
}
t.Fatal("expected session cookie after login")
return nil
}
func submitForm(t *testing.T, router http.Handler, method, target string, form url.Values, expectedStatus int, cookies ...*http.Cookie) string {
t.Helper()
if strings.HasPrefix(target, "/admin/") {
csrfCookie := ensureCSRFCookie(t, router, cookies...)
cookies = upsertCookie(cookies, csrfCookie)
if form == nil {
form = url.Values{}
}
if form.Get("csrf_token") == "" {
form.Set("csrf_token", csrfCookie.Value)
}
}
recorder := performRequest(t, router, method, target, form, cookies...)
if recorder.Code != expectedStatus {
t.Fatalf("expected %d for %s %s, got %d with body %s", expectedStatus, method, target, recorder.Code, recorder.Body.String())
}
return recorder.Header().Get("Location")
}
func submitMultipartForm(t *testing.T, router http.Handler, target string, fields map[string]string, fileField, filename string, body []byte, cookies ...*http.Cookie) string {
t.Helper()
if strings.HasPrefix(target, "/admin/") {
csrfCookie := ensureCSRFCookie(t, router, cookies...)
cookies = upsertCookie(cookies, csrfCookie)
if fields == nil {
fields = map[string]string{}
}
if fields["csrf_token"] == "" {
fields["csrf_token"] = csrfCookie.Value
}
}
var requestBody bytes.Buffer
writer := multipart.NewWriter(&requestBody)
for key, value := range fields {
if err := writer.WriteField(key, value); err != nil {
t.Fatalf("write multipart field %s: %v", key, err)
}
}
part, err := writer.CreateFormFile(fileField, filename)
if err != nil {
t.Fatalf("create multipart file part: %v", err)
}
if _, err := part.Write(body); err != nil {
t.Fatalf("write multipart file body: %v", err)
}
if err := writer.Close(); err != nil {
t.Fatalf("close multipart writer: %v", err)
}
req := httptest.NewRequest(http.MethodPost, target, &requestBody)
req.RemoteAddr = "127.0.0.1:12345"
req.Header.Set("Content-Type", writer.FormDataContentType())
for _, cookie := range cookies {
req.AddCookie(cookie)
}
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, req)
if recorder.Code != http.StatusSeeOther {
t.Fatalf("expected multipart upload redirect, got %d with body %s", recorder.Code, recorder.Body.String())
}
return recorder.Header().Get("Location")
}
func upsertCookie(cookies []*http.Cookie, cookie *http.Cookie) []*http.Cookie {
if cookie == nil {
return cookies
}
updated := make([]*http.Cookie, 0, len(cookies)+1)
replaced := false
for _, existing := range cookies {
if existing == nil {
continue
}
if existing.Name == cookie.Name {
updated = append(updated, cookie)
replaced = true
continue
}
updated = append(updated, existing)
}
if !replaced {
updated = append(updated, cookie)
}
return updated
}
func extractResourceID(t *testing.T, location, prefix string) string {
t.Helper()
if !strings.HasPrefix(location, prefix) {
t.Fatalf("expected redirect location with prefix %q, got %q", prefix, location)
}
trimmed := strings.TrimPrefix(location, prefix)
parts := strings.SplitN(trimmed, "?", 2)
if parts[0] == "" {
t.Fatalf("could not extract resource id from %q", location)
}
return parts[0]
}
func mustParseInt64(t *testing.T, raw string) int64 {
t.Helper()
value, err := url.PathUnescape(raw)
if err != nil {
t.Fatalf("unescape id %q: %v", raw, err)
}
var parsed int64
if _, err := fmt.Sscan(value, &parsed); err != nil {
t.Fatalf("parse id %q: %v", raw, err)
}
return parsed
}