feat(aether): initial commit – self-hosted Cloud, Phasen 1–7
Go-Backend (chi, pgx, golang-migrate) + React/TS-Frontend als PWA. Enthalten: Auth inkl. OIDC, Dateien mit Versionen und Papierkorb, Freigaben (Links + intern), WebDAV, Gruppenordner, Admin/Theming, Volltextsuche mit OCR, eigener PDF-Betrachter und WOPI-Office (Collabora/OnlyOffice). Nicht im Repo: deploy/.env (Sitzungsschlüssel, DB- und Admin-Passwort) sowie alle Daten- und Build-Verzeichnisse – siehe .gitignore. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
commit
31a027c93a
225 files changed
+38042
No files matched your search
@@ -0,0 +1,64 @@
|
||||
package webdav
|
||||
|
||||
import "testing"
|
||||
|
||||
// dedupe vergibt Namen für gleichnamige Freigaben. Entscheidend ist, dass die
|
||||
// Vergabe bei gleichem Bestand immer gleich ausfällt: Ein Sync-Client, dem sich
|
||||
// Pfade zwischen zwei Läufen verschieben, lädt alles neu hoch oder löscht es.
|
||||
func TestDedupeIsStable(t *testing.T) {
|
||||
in := []sharedEntry{
|
||||
{owner: "anna", name: "Projekt"},
|
||||
{owner: "ben", name: "Projekt"},
|
||||
{owner: "anna", name: "Projekt"},
|
||||
{owner: "anna", name: "Notizen"},
|
||||
}
|
||||
first := namesOf(dedupe(append([]sharedEntry(nil), in...)))
|
||||
|
||||
// Andere Eingabereihenfolge, gleicher Bestand → gleiches Ergebnis.
|
||||
shuffled := []sharedEntry{in[3], in[2], in[1], in[0]}
|
||||
second := namesOf(dedupe(shuffled))
|
||||
|
||||
if len(first) != len(second) {
|
||||
t.Fatalf("unterschiedlich viele Einträge: %v vs %v", first, second)
|
||||
}
|
||||
for i := range first {
|
||||
if first[i] != second[i] {
|
||||
t.Fatalf("Reihenfolge nicht stabil:\n %v\n %v", first, second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDedupeMakesNamesUniquePerOwner(t *testing.T) {
|
||||
got := namesOf(dedupe([]sharedEntry{
|
||||
{owner: "anna", name: "Projekt"},
|
||||
{owner: "anna", name: "Projekt"},
|
||||
{owner: "anna", name: "Projekt"},
|
||||
// Gleicher Name bei einem anderen Eigentümer ist kein Konflikt – die
|
||||
// beiden liegen in verschiedenen Verzeichnissen.
|
||||
{owner: "ben", name: "Projekt"},
|
||||
}))
|
||||
want := []string{"anna/Projekt", "anna/Projekt (2)", "anna/Projekt (3)", "ben/Projekt"}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Errorf("Eintrag %d = %q, erwartet %q", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// owners liefert die zweite Ebene unter /Shared – jede Person genau einmal.
|
||||
func TestOwnersAreUnique(t *testing.T) {
|
||||
got := owners([]sharedEntry{
|
||||
{owner: "anna"}, {owner: "anna"}, {owner: "ben"}, {owner: "anna"},
|
||||
})
|
||||
if len(got) != 2 || got[0] != "anna" || got[1] != "ben" {
|
||||
t.Fatalf("owners = %v, erwartet [anna ben]", got)
|
||||
}
|
||||
}
|
||||
|
||||
func namesOf(entries []sharedEntry) []string {
|
||||
out := make([]string, len(entries))
|
||||
for i, e := range entries {
|
||||
out[i] = e.owner + "/" + e.name
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in new issue
Block a user