Drei neue Ende-zu-Ende-Tests: das Verwalten mehrerer Repositories samt Datentrennung, das Lesen von Exportdateien der Version 1, und die Uebernahme ohne verknuepftes Repository. Dabei fiel auf, dass der Uebernahme-Endpunkt ohne Repository mit 404 antwortete, obwohl gar keine Repository-ID angefragt war. Wer nichts angibt, hat schlicht noch keines verknuepft - das ist kein "nicht gefunden", sondern 409 mit dem Hinweis, eines zu hinterlegen. Version 1.0.0: der Funktionsumfang ist beisammen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -69,15 +69,31 @@ SSRF-Schutz (`lib/security/ssrf.ts`).
|
||||
`prisma migrate dev` ist hier nicht benutzbar (interaktiv). Stattdessen:
|
||||
|
||||
```bash
|
||||
npx prisma migrate diff --from-schema-datasource prisma/schema.prisma \
|
||||
--to-schema-datamodel prisma/schema.prisma --script --output prisma/migrations/<stempel>_<name>/migration.sql
|
||||
docker exec nebula-db psql -U nebula -d postgres -c "CREATE DATABASE nebula_shadow"
|
||||
npx prisma migrate diff --from-migrations prisma/migrations \
|
||||
--to-schema-datamodel prisma/schema.prisma \
|
||||
--shadow-database-url postgresql://nebula:nebula@localhost:5432/nebula_shadow \
|
||||
--script --output prisma/migrations/<stempel>_<name>/migration.sql
|
||||
npx prisma migrate deploy
|
||||
```
|
||||
|
||||
**`--from-migrations`, nicht `--from-schema-datasource`.** Letzteres
|
||||
vergleicht mit der *laufenden* Datenbank. Ist eine eben erzeugte Migration
|
||||
dort noch nicht eingespielt, steht ihr Inhalt in der nächsten gleich noch
|
||||
einmal.
|
||||
|
||||
**Die erzeugte SQL vor dem Anwenden lesen.** Der Volltextindex
|
||||
`Note_fulltext_idx` ist ein Ausdrucksindex, den Prisma nicht kennt (er wird
|
||||
bislang in Ruhe gelassen, aber das ist nicht garantiert).
|
||||
|
||||
**Datenmigrationen von Hand schreiben.** `migrate diff` kennt nur den
|
||||
Zielzustand: Spalten, die woandershin wandern, würde es ersatzlos löschen.
|
||||
Vorgehen wie in `20260726230000_repositories`: neue Tabelle anlegen, Bestand
|
||||
per `INSERT … SELECT` übernehmen, dann erst die alten Spalten entfernen.
|
||||
Danach `migrate diff` erneut laufen lassen — kommt „empty migration“ heraus,
|
||||
stimmt das Ergebnis mit dem Schema überein. Und gegen eine Kopie mit echten
|
||||
Daten prüfen, nicht nur gegen eine leere Datenbank.
|
||||
|
||||
## Prüfen, nicht behaupten
|
||||
|
||||
Vor „fertig": `npx tsc --noEmit`, `npm test`, `npm run build`.
|
||||
@@ -86,6 +102,19 @@ Bei Änderungen an der Oberfläche **hinsehen**. Chrome ist installiert, ein
|
||||
Skript mit `playwright-core` und `channel: "chrome"` genügt für Screenshots.
|
||||
Mehrere Anzeigefehler dieses Projekts sind ausschließlich so aufgefallen.
|
||||
|
||||
Wer den Standalone-Bau von Hand startet (`node .next/standalone/server.js`),
|
||||
muss vorher `.next/static` und `public` danebenkopieren — sonst lädt kein
|
||||
JavaScript, die Seite bleibt serverseitig gerendert, und Formulare senden
|
||||
sich nativ ab. Das sieht aus wie ein Anwendungsfehler, ist aber keiner:
|
||||
|
||||
```bash
|
||||
cp -r .next/static .next/standalone/.next/ && cp -r public .next/standalone/
|
||||
```
|
||||
|
||||
`pkill` gibt es in dieser Git-Bash nicht. Prozesse über PowerShell beenden
|
||||
(`Get-CimInstance Win32_Process … | Stop-Process`) — sonst läuft der alte
|
||||
Server weiter, hält die Prisma-DLL und blockiert den nächsten Bau.
|
||||
|
||||
**Niemals gegen die echte Datenbank testen.** Eine Wegwerf-Datenbank anlegen
|
||||
und danach löschen:
|
||||
|
||||
|
||||
+43
-6
@@ -27,6 +27,14 @@ sonst würden Konten ausgesperrt. Nach der Einrichtung ist `/setup` dauerhaft ge
|
||||
- **Passkey (WebAuthn).** Fingerabdruck, Gesichtserkennung oder Sicherheitsschlüssel. Die
|
||||
Anmeldung läuft über auffindbare Credentials, ein Benutzername ist also nicht nötig.
|
||||
Mehrere Passkeys pro Konto, benennbar und einzeln entfernbar.
|
||||
- **Zwei-Faktor per TOTP.** Zeitbasiertes Einmalkennwort zusätzlich zum Passwort, für jede
|
||||
gängige Authenticator-App. Einrichtung über QR-Code oder abgetippten Schlüssel; scharf
|
||||
wird er erst nach einem bestätigten Code, damit sich niemand aussperrt, der den Scan
|
||||
nicht hinbekommt. Dazu zehn **Wiederherstellungscodes**, jeder genau einmal gültig —
|
||||
sie werden nur bei der Ausgabe angezeigt. Abschalten und Neuerzeugen verlangen das
|
||||
Passwort, nicht bloß eine offene Sitzung.
|
||||
- Die Passkey-Anmeldung fragt **nicht** nach dem zweiten Faktor: der Passkey ist bereits an
|
||||
ein Gerät gebunden und damit selbst der zweite Faktor.
|
||||
- Ein Konto kann **ausschließlich** per Passkey nutzbar sein. Der letzte Passkey lässt sich
|
||||
dann nicht entfernen, solange kein Passwort gesetzt ist.
|
||||
- Fehlversuche werden gezählt; nach acht sperrt sich das Konto für 15 Minuten.
|
||||
@@ -35,9 +43,9 @@ sonst würden Konten ausgesperrt. Nach der Einrichtung ist `/setup` dauerhaft ge
|
||||
|
||||
### 0.3 Mein Konto
|
||||
|
||||
Anzeigename und E-Mail ändern · Passwort setzen oder wechseln · Passkeys verwalten ·
|
||||
aktive Sitzungen einsehen (Browser, Betriebssystem, IP, letzte Aktivität) · überall
|
||||
abmelden, wobei das eigene Gerät angemeldet bleibt.
|
||||
Anzeigename und E-Mail ändern · Passwort setzen oder wechseln · Zwei-Faktor einrichten und
|
||||
abschalten · Passkeys verwalten · aktive Sitzungen einsehen (Browser, Betriebssystem, IP,
|
||||
letzte Aktivität) · überall abmelden, wobei das eigene Gerät angemeldet bleibt.
|
||||
|
||||
### 0.4 Benutzerverwaltung (Administratoren)
|
||||
|
||||
@@ -215,6 +223,31 @@ statt verschoben.
|
||||
|
||||
## 4. Git-Anbindung
|
||||
|
||||
### 4.0 Mehrere Repositories je Projekt
|
||||
|
||||
Ein Vorhaben besteht oft aus mehreren Repositories — Frontend und Backend etwa, oder
|
||||
App und Infrastruktur. Sie gehören trotzdem in **eine** Karte.
|
||||
|
||||
- Im Git-Panel steht eine Leiste mit allen Repositories des Projekts, sobald es mehr als
|
||||
eines gibt. Jedes hat eigene Kennzahlen, Listen, CI und eigenen Abgleich; „Alle" gleicht
|
||||
sie nacheinander ab — nicht gleichzeitig, denn ein Schwung Anfragen an denselben Anbieter
|
||||
ist genau das, worauf Rate-Limits reagieren.
|
||||
- Eine frei wählbare **Bezeichnung** („Frontend") tritt an die Stelle von `owner/name`.
|
||||
- Das erste Repository ist das **führende**. Wo nur eines dargestellt werden kann — auf der
|
||||
Karte im Dashboard, im Kanban —, ist es dieses. Die Karte zeigt daneben `+2 Repos`.
|
||||
Ein Abgleichsfehler an *irgendeinem* Repository erscheint auf der Karte, sonst bliebe er
|
||||
unbemerkt.
|
||||
- **Stillstand** bemisst sich am jüngsten Commit über alle Repositories: an einem Vorhaben
|
||||
wird gearbeitet, sobald an einem seiner Teile gearbeitet wird.
|
||||
- Benachrichtigungen merken sich den gemeldeten Zustand **je Repository**. Sonst verdeckte
|
||||
ein rotes Frontend die rote Pipeline des Backends. Der Betreff nennt das Repository nur,
|
||||
wenn es nicht ohnehin so heißt wie das Projekt.
|
||||
- Übernommene Issues merken sich, aus welchem Repository sie stammen — Frontend und Backend
|
||||
haben beide ein Issue Nummer 5, und das sind zwei Aufgaben.
|
||||
|
||||
Der Projektdialog bearbeitet weiterhin nur das führende Repository; weitere verwaltet das
|
||||
Git-Panel. Beim Anlegen eines Projekts entsteht aus den Git-Feldern das erste.
|
||||
|
||||
### 4.1 Unterstützte Systeme
|
||||
|
||||
| System | API | Authentifizierung | Besonderheit |
|
||||
@@ -441,13 +474,17 @@ letzten Zustellung stehen an der Karte.
|
||||
| `Session` | Serverseitige Sitzungen (nur der Token-Hash) |
|
||||
| `Credential` | Passkeys inkl. öffentlichem Schlüssel und Signaturzähler |
|
||||
| `WebAuthnChallenge` | Kurzlebige, einmalig verwendbare Challenges |
|
||||
| `RecoveryCode` | Wiederherstellungscodes für die Zwei-Faktor-Anmeldung (nur der Hash) |
|
||||
| `PendingLogin` | Zwischenzustand zwischen Passwort und zweitem Faktor, fünf Minuten gültig |
|
||||
| `AppSettings` | Betriebsart und Registrierung (eine Zeile) |
|
||||
| `Project` | Projektstammdaten, Status, Fortschritt, Repo-Verknüpfung, Besitzer |
|
||||
| `Task` | Aufgaben mit Spalte, Position, Fälligkeit und Labels |
|
||||
| `Project` | Projektstammdaten, Status, Fortschritt, Besitzer |
|
||||
| `Repository` | Repositories eines Projekts inkl. Abgleichstand (n:1 zu Projekt) |
|
||||
| `Task` | Aufgaben mit Spalte, Position, Fälligkeit, Labels und Issue-Herkunft |
|
||||
| `Activity` | Verlauf je Projekt, neueste zuerst, auf 200 Einträge begrenzt |
|
||||
| `NotificationTarget` | Zustellwege je Konto, mit verschlüsseltem Geheimnis |
|
||||
| `Note` | Notizen (n:1 zu Projekt, Cascade-Delete) |
|
||||
| `GitConnection` | Git-Systeme inkl. verschlüsseltem Token, Besitzer |
|
||||
| `RepoSnapshot` | Zwischengespeicherter Repo-Stand (1:1 zu Projekt) |
|
||||
| `RepoSnapshot` | Zwischengespeicherter Repo-Stand (1:1 zu Repository) |
|
||||
|
||||
`Project.ownerId` und `GitConnection.ownerId` sind bewusst optional: so überleben Daten aus
|
||||
der Zeit vor der Benutzerverwaltung die Migration und werden beim Anlegen des ersten Kontos
|
||||
|
||||
@@ -324,12 +324,15 @@ ausschließlich Daten des angemeldeten Kontos.
|
||||
| `GET` | `/api/health` | Status inkl. Datenbankverbindung (Docker-Healthcheck) |
|
||||
| `POST` | `/api/auth/setup` | Administratorkonto anlegen (nur beim ersten Mal) |
|
||||
| `POST` | `/api/auth/login` `/api/auth/logout` `/api/auth/register` | Anmeldung |
|
||||
| `POST` | `/api/auth/mfa` | Zweiter Faktor: Einmalkennwort oder Wiederherstellungscode |
|
||||
| `GET` | `/api/auth/session` | Aktuelle Sitzung und Instanzzustand |
|
||||
| `POST` | `/api/auth/passkey/{login,register}/{options,verify}` | WebAuthn |
|
||||
| `PATCH` | `/api/account/profile` | Eigenes Profil |
|
||||
| `POST` | `/api/account/password` | Eigenes Passwort setzen |
|
||||
| `GET` `DELETE` | `/api/account/sessions` | Eigene Sitzungen / überall abmelden |
|
||||
| `PATCH` `DELETE` | `/api/account/passkeys/:id` | Passkey umbenennen / entfernen |
|
||||
| `GET` `POST` `PUT` `DELETE` | `/api/account/totp` | Zwei-Faktor: Zustand, einrichten, aktivieren, abschalten |
|
||||
| `POST` | `/api/account/totp/recovery` | Neue Wiederherstellungscodes |
|
||||
| `GET` `PATCH` | `/api/admin/settings` | Betriebsart, Registrierung, Abgleich (Admin) |
|
||||
| `POST` | `/api/admin/sync/run` | Abgleich sofort anstoßen (Admin) |
|
||||
| `GET` `POST` | `/api/admin/users` | Konten auflisten / anlegen (Admin) |
|
||||
@@ -344,7 +347,11 @@ ausschließlich Daten des angemeldeten Kontos.
|
||||
| `PATCH` `DELETE` | `/api/notifications/:id` | Ziel ändern / entfernen |
|
||||
| `POST` | `/api/notifications/:id/test` | Testnachricht senden |
|
||||
| `GET` `PATCH` `DELETE` | `/api/projects/:id` | Projekt lesen / ändern / löschen |
|
||||
| `POST` | `/api/projects/:id/sync` | Repo-Daten vom Git-System holen |
|
||||
| `POST` | `/api/projects/:id/sync` | Alle Repositories des Projekts abgleichen |
|
||||
| `GET` | `/api/projects/:id/activity` | Verlauf des Projekts (`?limit=`) |
|
||||
| `GET` `POST` | `/api/projects/:id/repositories` | Repositories des Projekts |
|
||||
| `PATCH` `DELETE` | `/api/repositories/:id` | Repository ändern / vom Projekt lösen |
|
||||
| `POST` | `/api/repositories/:id/sync` | Ein einzelnes Repository abgleichen |
|
||||
| `GET` `POST` | `/api/projects/:id/tasks` | Aufgaben des Projekts |
|
||||
| `PATCH` | `/api/projects/:id/tasks/reorder` | Neue Reihenfolge nach dem Ziehen |
|
||||
| `POST` | `/api/projects/:id/tasks/from-issues` | Offene Issues als Aufgaben übernehmen |
|
||||
|
||||
+2
-12
@@ -37,17 +37,7 @@ Frei sortieren hieße, eine Position je Projekt zu speichern.
|
||||
|
||||
## 3 · Größere Brocken
|
||||
|
||||
### 3.1 Mehrere Repositories je Projekt `M`
|
||||
|
||||
Frontend und Backend getrennt, aber ein Projekt. Erfordert eine Zwischentabelle
|
||||
und Anpassungen in der Anzeige.
|
||||
|
||||
### 3.2 Zwei-Faktor per TOTP `M`
|
||||
|
||||
Als Alternative zum Passkey — nützlich, wenn die Instanz aus dem Internet
|
||||
erreichbar ist und nicht jedes Gerät Passkeys kann.
|
||||
|
||||
### 3.3 Passwort-Zurücksetzen per E-Mail `M`
|
||||
### 3.1 Passwort-Zurücksetzen per E-Mail `M`
|
||||
|
||||
Heute setzt ein Administrator das Passwort neu. Selbstbedienung braucht SMTP
|
||||
und damit eine weitere Konfiguration — lohnt erst im Mehrbenutzerbetrieb.
|
||||
@@ -65,7 +55,6 @@ Voraussetzung, falls du das Projekt veröffentlichen willst.
|
||||
| --- | --- | --- |
|
||||
| Suchtreffer in Notizen hervorheben | `S` | In der Notiz selbst, nicht nur in der Trefferliste |
|
||||
| Mehrfachauswahl im Board | `S` | Status oder Tags für mehrere auf einmal |
|
||||
| Aktivitätsverlauf je Projekt | `S` | Wer hat wann was geändert |
|
||||
| Wiederkehrende Aufgaben | `M` | Etwa „jeden Montag Abhängigkeiten prüfen" |
|
||||
| Projektvorlagen | `S` | Neues Projekt mit vorgefüllten Tags und Notizen |
|
||||
| Hell/Dunkel umschaltbar | `M` | Aktuell nur dunkel |
|
||||
@@ -111,6 +100,7 @@ Damit klar ist, was **nicht** kommt und warum:
|
||||
| **v0.7.0** Benachrichtigungen über ntfy, Webhook und E-Mail | ✓ |
|
||||
| **v0.8.0** Stillstehende Projekte melden, Schwelle im Adminbereich | ✓ |
|
||||
| **v0.9.0** Schnellsuche per `Strg+K`, Aufgaben aus Git-Issues | ✓ |
|
||||
| **v1.0.0** Aktivitätsverlauf je Projekt, Zwei-Faktor per TOTP, mehrere Repositories je Projekt | ✓ |
|
||||
|
||||
Zur Entscheidung beim Export: Git-Tokens gehen **nicht** mit. Die Verbindung wird nur
|
||||
beschrieben, das Token ist nach dem Import neu zu hinterlegen. Snapshots bleiben
|
||||
|
||||
+121
-1
@@ -191,11 +191,92 @@ test("Issues als Aufgaben uebernehmen, ohne Dubletten", async ({ request }) => {
|
||||
await (await request.post("/api/projects", { data: { name: "Issue-Projekt" } })).json()
|
||||
).project;
|
||||
|
||||
// Ohne Repo-Stand gibt es nichts zu uebernehmen – und das sagt der Server auch.
|
||||
// Ohne verknuepftes Repository gibt es nichts zu uebernehmen.
|
||||
const ohneRepo = await request.post(`/api/projects/${projekt.id}/tasks/from-issues`, {
|
||||
data: {},
|
||||
});
|
||||
expect(ohneRepo.status()).toBe(409);
|
||||
|
||||
// Mit Repository, aber ohne geholten Stand ebenfalls nicht – der Server
|
||||
// laedt hier bewusst nichts nach.
|
||||
await request.patch(`/api/projects/${projekt.id}`, {
|
||||
data: { repoUrl: "https://github.com/vercel/swr" },
|
||||
});
|
||||
const ohneStand = await request.post(`/api/projects/${projekt.id}/tasks/from-issues`, {
|
||||
data: {},
|
||||
});
|
||||
expect(ohneStand.status()).toBe(409);
|
||||
|
||||
// Nach einem bestimmten, das es nicht gibt, ist dagegen 404 richtig.
|
||||
const fremdesRepo = await request.post(`/api/projects/${projekt.id}/tasks/from-issues`, {
|
||||
data: { repositoryId: "gibt-es-nicht" },
|
||||
});
|
||||
expect(fremdesRepo.status()).toBe(404);
|
||||
});
|
||||
|
||||
test("Mehrere Repositories je Projekt", async ({ request, playwright }) => {
|
||||
await login(request, ADMIN);
|
||||
|
||||
// Beim Anlegen setzen die Git-Felder das fuehrende Repository.
|
||||
const projekt = (
|
||||
await (
|
||||
await request.post("/api/projects", {
|
||||
data: { name: "Zwei Teile", repoUrl: "https://github.com/vercel/swr" },
|
||||
})
|
||||
).json()
|
||||
).project;
|
||||
|
||||
expect(projekt.repositories).toHaveLength(1);
|
||||
expect(projekt.repositories[0].repoOwner).toBe("vercel");
|
||||
expect(projekt.repositories[0].repoName).toBe("swr");
|
||||
|
||||
// Ein zweites dazu.
|
||||
const zweites = await request.post(`/api/projects/${projekt.id}/repositories`, {
|
||||
data: { label: "Backend", repoUrl: "https://github.com/vercel/next.js" },
|
||||
});
|
||||
expect(zweites.status()).toBe(201);
|
||||
const backendId = (await zweites.json()).repository.id;
|
||||
|
||||
// Dasselbe Repo zweimal ist immer ein Versehen.
|
||||
const doppelt = await request.post(`/api/projects/${projekt.id}/repositories`, {
|
||||
data: { repoUrl: "https://github.com/vercel/next.js" },
|
||||
});
|
||||
expect(doppelt.status()).toBe(409);
|
||||
|
||||
// Halbe Angaben ergeben kein Repository.
|
||||
const leer = await request.post(`/api/projects/${projekt.id}/repositories`, {
|
||||
data: { label: "Nur ein Name" },
|
||||
});
|
||||
expect(leer.status()).toBe(422);
|
||||
|
||||
const geladen = (await (await request.get(`/api/projects/${projekt.id}`)).json()).project;
|
||||
expect(geladen.repositories).toHaveLength(2);
|
||||
// Das fuehrende steht vorn.
|
||||
expect(geladen.repositories[0].repoName).toBe("swr");
|
||||
expect(geladen.repositories[1].label).toBe("Backend");
|
||||
|
||||
// Der Projekt-PATCH bearbeitet weiterhin nur das fuehrende.
|
||||
await request.patch(`/api/projects/${projekt.id}`, { data: { autoSync: false } });
|
||||
const danach = (await (await request.get(`/api/projects/${projekt.id}`)).json()).project;
|
||||
expect(danach.repositories[0].autoSync).toBe(false);
|
||||
expect(danach.repositories[1].autoSync).toBe(true);
|
||||
|
||||
// Fremde Konten kommen an kein Repository.
|
||||
const fremd = await playwright.request.newContext({
|
||||
baseURL: test.info().project.use.baseURL,
|
||||
});
|
||||
await login(fremd, ZWEITER);
|
||||
expect((await fremd.get(`/api/projects/${projekt.id}/repositories`)).status()).toBe(404);
|
||||
expect((await fremd.patch(`/api/repositories/${backendId}`, { data: {} })).status()).toBe(404);
|
||||
expect((await fremd.delete(`/api/repositories/${backendId}`)).status()).toBe(404);
|
||||
expect((await fremd.post(`/api/repositories/${backendId}/sync`)).status()).toBe(404);
|
||||
await fremd.dispose();
|
||||
|
||||
// Entfernen laesst das andere stehen.
|
||||
expect((await request.delete(`/api/repositories/${backendId}`)).ok()).toBeTruthy();
|
||||
const zumSchluss = (await (await request.get(`/api/projects/${projekt.id}`)).json()).project;
|
||||
expect(zumSchluss.repositories).toHaveLength(1);
|
||||
expect(zumSchluss.repositories[0].repoName).toBe("swr");
|
||||
});
|
||||
|
||||
test("Export enthaelt keine Geheimnisse, Import beachtet die Strategie", async ({ request }) => {
|
||||
@@ -218,6 +299,12 @@ test("Export enthaelt keine Geheimnisse, Import beachtet die Strategie", async (
|
||||
const datei = JSON.parse(roh);
|
||||
const vorher = datei.projects.length;
|
||||
|
||||
// Version 2: die Repositories stehen als Liste in der Datei.
|
||||
expect(datei.version).toBe(2);
|
||||
const mitRepo = datei.projects.find((entry: { name: string }) => entry.name === "Zwei Teile");
|
||||
expect(mitRepo.repositories).toHaveLength(1);
|
||||
expect(mitRepo.repositories[0].repoName).toBe("swr");
|
||||
|
||||
// Ueberspringen aendert nichts.
|
||||
const uebersprungen = await request.post("/api/projects/import", {
|
||||
data: { data: datei, strategy: "skip", dryRun: false },
|
||||
@@ -239,3 +326,36 @@ test("Export enthaelt keine Geheimnisse, Import beachtet die Strategie", async (
|
||||
});
|
||||
expect(muell.status()).toBe(422);
|
||||
});
|
||||
|
||||
test("Dateien der Version 1 bleiben lesbar", async ({ request }) => {
|
||||
await login(request, ADMIN);
|
||||
|
||||
// Damals stand genau ein Repository flach am Projekt. Wer eine solche
|
||||
// Sicherung liegen hat, soll sie weiterhin einspielen koennen.
|
||||
const alt = {
|
||||
kind: "nebula.projects",
|
||||
version: 1,
|
||||
projects: [
|
||||
{
|
||||
name: "Aus Version eins",
|
||||
repoUrl: "https://github.com/vercel/swr",
|
||||
repoOwner: "vercel",
|
||||
repoName: "swr",
|
||||
autoSync: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const gelesen = await request.post("/api/projects/import", {
|
||||
data: { data: alt, strategy: "rename", dryRun: false },
|
||||
});
|
||||
expect(gelesen.ok()).toBeTruthy();
|
||||
|
||||
const projekte = (await (await request.get("/api/projects")).json()).projects;
|
||||
const uebernommen = projekte.find(
|
||||
(entry: { name: string }) => entry.name === "Aus Version eins",
|
||||
);
|
||||
expect(uebernommen.repositories).toHaveLength(1);
|
||||
expect(uebernommen.repositories[0].repoOwner).toBe("vercel");
|
||||
expect(uebernommen.repositories[0].autoSync).toBe(false);
|
||||
});
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "nebula",
|
||||
"version": "0.9.0",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "Nebula – Dashboard fuer Vibe-Coding Projekte mit Git-Anbindung (GitHub, GitLab, Gitea)",
|
||||
"scripts": {
|
||||
|
||||
Reference in New Issue
Block a user