# Testplan — Favoritter ## Dekning (oppdatert 2026-04-03) | Lag | Pakke | Testfiler | Testfunksjoner | Dekning | |-----|-------|-----------|----------------|---------| | Data | `internal/store` | 6 | 24 | 70.8 % | | HTTP (web) | `internal/handler` | 2 | 55 | 56.1 % | | HTTP (API) | `internal/handler/api` | 1 | 28 | 75.8 % | | Mellomvare | `internal/middleware` | 3 | 29 | 83.1 % | | Konfig | `internal/config` | 1 | 8 | 81.1 % | | Bilde | `internal/image` | 1 | 10 | 85.7 % | | Database | `internal/database` | 1 | 6 | 66.0 % | | Rendering | `internal/render` | 1 | 6 | 72.7 % | | Modell | `internal/model` | 0 | 0 | 0.0 % (kun datatyper) | **Totalt**: 16 testfiler, 167 testfunksjoner, alle grønne. --- ## Prioriteter - **P0 — Kritisk**: Sikkerhet, autentisering, autorisasjon - **P1 — Høy**: Forretningslogikk, dataintegritet, API-kontrakter - **P2 — Middels**: Konfigurasjon, feilhåndtering, kanttilfeller - **P3 — Lav**: Rendering, logging, hjelpefunksjoner --- ## P0 — Sikkerhet og tilgangskontroll ### CSRF-beskyttelse (`internal/middleware/csrf.go`) | # | Test | Verifiserer | |---|------|-------------| | 1 | `TestCSRFTokenSetInCookie` | POST uten eksisterende cookie → ny `csrf_token`-cookie settes | | 2 | `TestCSRFValidTokenAccepted` | POST med matchende cookie + form-felt → 200 | | 3 | `TestCSRFMismatchRejected` | POST med feil token i form-felt → 403 | | 4 | `TestCSRFMissingTokenRejected` | POST uten token i form-felt/header → 403 | | 5 | `TestCSRFHeaderFallback` | POST med token i `X-CSRF-Token`-header → 200 | | 6 | `TestCSRFSkippedForAPI` | POST til `/api/`-ruter → CSRF sjekkes ikke | | 7 | `TestCSRFSafeMethodsPassThrough` | GET/HEAD/OPTIONS → ingen validering | ### Auth-mellomvare (`internal/middleware/auth.go`) | # | Test | Verifiserer | |---|------|-------------| | 1 | `TestSessionLoaderValidToken` | Gyldig session-cookie → bruker satt i context | | 2 | `TestSessionLoaderInvalidToken` | Ugyldig cookie → context uten bruker, ingen feil | | 3 | `TestSessionLoaderNoCookie` | Ingen cookie → context uten bruker | | 4 | `TestRequireLoginRedirectsToLogin` | Uautentisert → 302 til `/login` | | 5 | `TestRequireLoginAllowsAuthenticated` | Autentisert → neste handler kjøres | | 6 | `TestRequireAdminRejectsNonAdmin` | Vanlig bruker → 403 | | 7 | `TestRequireAdminAllowsAdmin` | Admin-bruker → neste handler kjøres | ### API-autentisering (`internal/handler/api/`) | # | Test | Verifiserer | |---|------|-------------| | 1 | `TestAPILoginSuccess` | Korrekt brukernavn/passord → token + brukerinfo | | 2 | `TestAPILoginWrongPassword` | Feil passord → 401 | | 3 | `TestAPILoginInvalidBody` | Ugyldig JSON → 400 | | 4 | `TestAPILogout` | Gyldig session → session slettet, cookie fjernet | | 5 | `TestAPIRequiresAuth` | Beskyttede endepunkter uten session → 401/redirect | ### Autorisasjon (eierskap) | # | Test | Verifiserer | |---|------|-------------| | 1 | `TestCannotEditOtherUsersFave` | PUT `/api/v1/faves/{id}` med annen bruker → 403 | | 2 | `TestCannotDeleteOtherUsersFave` | DELETE `/api/v1/faves/{id}` med annen bruker → 403 | | 3 | `TestPrivateFaveHiddenFromAPI` | GET `/api/v1/faves/{id}` privat fave, annen bruker → 404 | | 4 | `TestPrivateFaveVisibleToOwnerAPI` | GET `/api/v1/faves/{id}` privat fave, eier → 200 | | 5 | `TestAdminEndpointsRequireAdminRole` | Ikke-admin → 403 på alle admin-ruter | --- ## P1 — Forretningslogikk og API-kontrakter ### Store: Innstillinger (`internal/store/settings.go`) | # | Test | Verifiserer | |---|------|-------------| | 1 | `TestSettingsGetDefault` | Hent standardinnstillinger etter migrasjon | | 2 | `TestSettingsUpdate` | Oppdater site_name, description, signup_mode → endringer leses tilbake | | 3 | `TestSettingsUpdatePartial` | Oppdater kun ett felt → andre felter beholdes | ### API: Favoritter CRUD (`internal/handler/api/`) | # | Test | Verifiserer | |---|------|-------------| | 1 | `TestAPICreateFave` | POST med beskrivelse, URL, privacy, tags → 201 + komplett JSON | | 2 | `TestAPICreateFaveMinimal` | POST med kun beskrivelse → 201, standardverdier | | 3 | `TestAPICreateFaveMissingDescription` | POST uten beskrivelse → 400 | | 4 | `TestAPIGetFave` | GET `/api/v1/faves/{id}` → komplett JSON med tagger | | 5 | `TestAPIGetFaveNotFound` | GET med ugyldig ID → 404 | | 6 | `TestAPIUpdateFave` | PUT med endringer → oppdatert JSON | | 7 | `TestAPIUpdateFavePartial` | PUT med kun tags → andre felter uendret | | 8 | `TestAPIDeleteFave` | DELETE → 204 No Content | | 9 | `TestAPIDeleteFaveNotFound` | DELETE med ugyldig ID → 404 | | 10 | `TestAPIListFaves` | GET `/api/v1/faves` → paginert resultat med total | | 11 | `TestAPIListFavesPagination` | Sjekk page/limit-parametre og grense (limit ≤ 100) | ### API: Brukere og offentlige favoritter | # | Test | Verifiserer | |---|------|-------------| | 1 | `TestAPIGetUser` | GET `/api/v1/users/{username}` → brukerinfo | | 2 | `TestAPIGetUserNotFound` | Ukjent brukernavn → 404 | | 3 | `TestAPIGetDisabledUser` | Deaktivert bruker → 404 | | 4 | `TestAPIGetUserFaves` | GET `/api/v1/users/{username}/faves` → kun offentlige | | 5 | `TestAPIGetUserFavesEmpty` | Bruker uten favoritter → tom liste | ### API: Tagger | # | Test | Verifiserer | |---|------|-------------| | 1 | `TestAPISearchTags` | GET `/api/v1/tags?q=prefix` → matchende tagger | | 2 | `TestAPISearchTagsEmpty` | Søk uten treff → tom liste | | 3 | `TestAPISearchTagsLimit` | `limit`-parameter respekteres, maks 100 | ### API: Eksport/import | # | Test | Verifiserer | |---|------|-------------| | 1 | `TestAPIExport` | GET `/api/v1/export/json` → alle brukerens favoritter | | 2 | `TestAPIImportValid` | POST med JSON-array → imported/total-svar | | 3 | `TestAPIImportSkipsEmpty` | Oppføringer uten beskrivelse → hoppes over | | 4 | `TestAPIImportInvalidJSON` | Ugyldig JSON → 400 | | 5 | `TestAPIImportBodyLimit` | Stor body → avvist (MaxUploadSize) | ### Handler: Registrering og pålogging (`internal/handler/auth.go`) | # | Test | Verifiserer | |---|------|-------------| | 1 | `TestSignupPageRendering` | GET `/signup` → skjema med CSRF-token | | 2 | `TestSignupSuccess` | POST med gyldig data → registreringsforespørsel opprettet | | 3 | `TestSignupDuplicate` | Duplisert brukernavn → feilmelding | | 4 | `TestSignupDisabled` | `signup_mode=closed` → 403 eller redirect | | 5 | `TestPasswordResetFlow` | Bruker med must_reset → tvinges til å endre passord | | 6 | `TestPasswordChangeSuccess` | POST med korrekt gammelt + nytt passord → oppdatert | | 7 | `TestPasswordChangeMismatch` | Nytt passord ≠ bekreftelse → feilmelding | | 8 | `TestLogout` | POST `/logout` → session slettet, redirect til login | ### Handler: Favoritter CRUD (web) (`internal/handler/fave.go`) | # | Test | Verifiserer | |---|------|-------------| | 1 | `TestCreateFavePage` | GET `/faves/new` → skjema rendres | | 2 | `TestCreateFaveSubmit` | POST med gyldig data → redirect til favoritt | | 3 | `TestCreateFaveWithImage` | POST med multipart/bilde → bilde lagret | | 4 | `TestEditFavePage` | GET `/faves/{id}/edit` → skjema med eksisterende data | | 5 | `TestEditFaveSubmit` | POST med endringer → oppdatert | | 6 | `TestEditFaveNotOwner` | Annen bruker → 403 | | 7 | `TestDeleteFave` | POST `/faves/{id}/delete` → slettet, redirect | | 8 | `TestDeleteFaveNotOwner` | Annen bruker → 403 | | 9 | `TestFaveListPagination` | Navigasjon med page-parameter | ### Handler: Administrasjon (`internal/handler/admin.go`) | # | Test | Verifiserer | |---|------|-------------| | 1 | `TestAdminDashboard` | GET `/admin` → statistikk rendres | | 2 | `TestAdminUserList` | GET `/admin/users` → alle brukere vises | | 3 | `TestAdminDisableUser` | POST → bruker deaktiveres | | 4 | `TestAdminEnableUser` | POST → bruker reaktiveres | | 5 | `TestAdminApproveSignup` | POST → bruker opprettes, forespørsel fjernes | | 6 | `TestAdminRejectSignup` | POST → forespørsel fjernes, bruker opprettes ikke | | 7 | `TestAdminUpdateSettings` | POST → innstillinger oppdateres | ### Handler: Profil (`internal/handler/profile.go`) | # | Test | Verifiserer | |---|------|-------------| | 1 | `TestPublicProfileVisible` | Offentlig profil → bio, visningsnavn vist | | 2 | `TestLimitedProfileHidesBio` | Begrenset profil → bio skjult, merknad vist | | 3 | `TestProfileEditPage` | GET `/profile/edit` → skjema med gjeldende verdier | | 4 | `TestProfileUpdateDisplayName` | POST → visningsnavn oppdatert | | 5 | `TestProfileUpdateVisibility` | POST → synlighet endret | | 6 | `TestProfileAvatarUpload` | POST med bilde → avatar lagret | ### Handler: Feed (`internal/handler/feed.go`) | # | Test | Verifiserer | |---|------|-------------| | 1 | `TestGlobalFeedAtom` | GET `/feed` → gyldig Atom XML | | 2 | `TestUserFeedAtom` | GET `/{username}/feed` → kun brukerens offentlige | | 3 | `TestFeedExcludesPrivate` | Private favoritter utelates | | 4 | `TestFeedLinksUseExternalURL` | Lenker bruker `EXTERNAL_URL`, ikke localhost | ### Handler: Import/Eksport (web) (`internal/handler/import_export.go`) | # | Test | Verifiserer | |---|------|-------------| | 1 | `TestExportPageRendering` | GET `/export` → eksportvalg vist | | 2 | `TestExportJSON` | GET `/export/json` → JSON-array med alle faves | | 3 | `TestExportCSV` | GET `/export/csv` → gyldig CSV med header | | 4 | `TestImportJSON` | POST med JSON-fil → favoritter importert | | 5 | `TestImportCSV` | POST med CSV-fil → favoritter importert | | 6 | `TestImportDuplicateHandling` | Duplikater → håndteres uten feil | --- ## P2 — Konfigurasjon, bilde, database ### Konfigurasjon (`internal/config/config.go`) | # | Test | Verifiserer | |---|------|-------------| | 1 | `TestLoadDefaults` | Ingen env-variabler → fornuftige standardverdier | | 2 | `TestLoadFromEnv` | Alle env-variabler satt → config inneholder riktige verdier | | 3 | `TestTrustedProxiesParsing` | `TRUSTED_PROXIES=10.0.0.0/8,192.168.0.0/16` → korrekte IPNet | | 4 | `TestTrustedProxiesInvalid` | Ugyldig CIDR → logges, hoppes over | | 5 | `TestBasePathNormalization` | Trailing slash fjernes, tom streng beholdes | | 6 | `TestSessionLifetimeParsing` | `SESSION_LIFETIME=48h` → korrekt Duration | | 7 | `TestDevModeFlag` | `FAVORITTER_DEV_MODE=true` → DevMode = true | | 8 | `TestMaxUploadSizeParsing` | `MAX_UPLOAD_SIZE=10485760` → korrekt int64 | ### Bildebehandling (`internal/image/image.go`) | # | Test | Verifiserer | |---|------|-------------| | 1 | `TestProcessJPEG` | Gyldig JPEG → UUID-filnavn, lagret i uploadDir | | 2 | `TestProcessPNG` | Gyldig PNG → re-encoded, EXIF strippet | | 3 | `TestProcessResizeWideImage` | Bilde > 1920px bredt → nedskalert til MaxWidth | | 4 | `TestProcessSmallImageNotResized` | Bilde < 1920px → beholdes som det er | | 5 | `TestProcessInvalidMIME` | `text/plain`-fil → feil returneres | | 6 | `TestProcessCorruptImage` | Korrupt bildedata → feil returneres | | 7 | `TestProcessUUIDFilename` | Filnavn bruker UUID, aldri brukerens opprinnelige filnavn | | 8 | `TestAllowedTypes` | Sjekk at JPEG, PNG, GIF, WebP er tillatt | ### Databasemigrasjoner (`internal/database/database.go`) | # | Test | Verifiserer | |---|------|-------------| | 1 | `TestOpenInMemory` | `:memory:` → fungerende tilkobling | | 2 | `TestMigrateCreatesAllTables` | Migrasjon → alle forventede tabeller finnes | | 3 | `TestMigrateIdempotent` | Kjør Migrate to ganger → ingen feil | | 4 | `TestPRAGMAs` | WAL-modus, foreign_keys, journal_size_limit satt | | 5 | `TestSingleConnection` | MaxOpenConns = 1 verifisert | ### Mellomvare: Context-hjelpere (`internal/middleware/context.go`) | # | Test | Verifiserer | |---|------|-------------| | 1 | `TestUserFromContextPresent` | Bruker i context → returneres | | 2 | `TestUserFromContextAbsent` | Ingen bruker → nil | | 3 | `TestCSRFTokenFromContext` | Token i context → returneres | | 4 | `TestFlashFromContext` | Flash-melding i context → returneres | ### Mellomvare: Logger (`internal/middleware/logger.go`) | # | Test | Verifiserer | |---|------|-------------| | 1 | `TestLoggerRecordsStatusCode` | Response status kodes logges | | 2 | `TestLoggerRecordsDuration` | Forespørselstid logges | | 3 | `TestLoggerIncludesPath` | URL-path inkluderes i loggoppføring | --- ## P3 — Rendering og hjelpefunksjoner ### Template-rendering (`internal/render/render.go`) | # | Test | Verifiserer | |---|------|-------------| | 1 | `TestRenderPage` | Rendrer side med layout → komplett HTML | | 2 | `TestRenderPageWithData` | PageData (Title, brukerinfo) → interpolert i template | | 3 | `TestRenderPartial` | Rendrer partial uten layout | | 4 | `TestRenderMissingTemplate` | Ukjent template-navn → feilhåndtering | | 5 | `TestCSRFTokenInTemplate` | CSRF-token tilgjengelig i template-context | ### API JSON-hjelpere (`internal/handler/api/`) | # | Test | Verifiserer | |---|------|-------------| | 1 | `TestJsonOKFormat` | Content-Type: application/json, gyldig JSON | | 2 | `TestJsonErrorFormat` | Feil → `{"error": "melding"}` + riktig statuskode | | 3 | `TestUserJSONPublicProfile` | Offentlig profil → bio og avatar inkludert | | 4 | `TestUserJSONLimitedProfile` | Begrenset profil → bio og avatar utelatt | | 5 | `TestFaveJSONComplete` | Alle felter mappet korrekt | | 6 | `TestQueryIntFallback` | Ugyldig/manglende parameter → standardverdi | | 7 | `TestQueryIntNegative` | Negativ verdi → standardverdi | --- ## Integrasjonstester (tverrgående) Disse testene verifiserer hele flyten fra HTTP-forespørsel gjennom mellomvare, handler, store og tilbake. | # | Test | Verifiserer | |---|------|-------------| | 1 | `TestFullLoginFlow` | GET login → POST credentials → session cookie → GET /faves → 200 | | 2 | `TestFullSignupApprovalFlow` | Registrering → admin godkjenner → bruker logger inn → endrer passord | | 3 | `TestFullFaveCRUDFlow` | Opprett → les → rediger → slett favoritt via web | | 4 | `TestFullAPIFaveCRUDFlow` | Login → opprett → hent → oppdater → slett via JSON API | | 5 | `TestImportExportRoundtrip` | Eksporter → importer til ny bruker → verifiser likhet | | 6 | `TestFeedReflectsNewFaves` | Opprett favoritt → feed oppdateres | | 7 | `TestRateLimitOnLogin` | Mange mislykkede pålogginger → 429 | | 8 | `TestCSRFProtectionEndToEnd` | Hent side → ekstraher token → POST med token → suksess | | 9 | `TestBasePathPropagation` | Alle lenker og redirects respekterer base path | | 10 | `TestMultiUserPrivacy` | Bruker A ser ikke bruker Bs private favoritter i noen visning | --- ## Testinfrastruktur ### Eksisterende hjelpere (gjenbruk) - `testDB(t)` — in-memory SQLite med migrasjoner (`store/*_test.go`) - `testServer(t)` — komplett handler-stack med mellomvare (`handler/handler_test.go`) - `loginUser(t, h, username, password, role)` — oppretter bruker og returnerer session-cookie - `extractCookie(rr, name)` — henter cookie fra response ### Nye hjelpere som trengs | Hjelper | Bruksområde | |---------|-------------| | `testAPIServer(t)` | Setup for API-handler med in-memory DB | | `apiLogin(t, server, username, password)` | Hent session-token for API-tester | | `testImage(t, width, height, mime)` | Generer test-bilde med gitte dimensjoner | | `setEnv(t, key, value)` | Sett env-variabel med automatisk cleanup | ### Konvensjoner - Bruk `:memory:` SQLite for alle tester - Sett raske Argon2-parametre (`Memory=1024, Time=1`) i alle passord-tester - Bruk `t.Helper()` på alle hjelpefunksjoner - Bruk `t.Cleanup()` for opprydding, aldri manuell defer i hjelpere - Bruk `httptest.NewRecorder()` og `httptest.NewRequest()` for HTTP-tester - Bruk `t.TempDir()` for bildebehandlingstester - Tabell-drevne tester der det er naturlig (f.eks. config-parsing, feilkoder) - Norske testbeskrivelser i kommentarer, engelske funksjons-/variabelnavn --- ## Implementeringsrekkefølge ``` Fase 1 (P0): Sikkerhet ├── middleware/csrf_test.go (~7 tester) ├── middleware/auth_test.go (~7 tester) ← utvid eksisterende fil └── handler/api/api_test.go (~5 auth-tester) Fase 2 (P1a): Store + API-kontrakter ├── store/settings_test.go (~3 tester) └── handler/api/api_test.go (~25 tester, utvid) Fase 3 (P1b): Web-handlere ├── handler/handler_test.go (~30 tester, utvid eksisterende) └── handler/auth_test.go (~8 tester) Fase 4 (P2): Konfig, bilde, database ├── config/config_test.go (~8 tester) ├── image/image_test.go (~8 tester) ├── database/database_test.go (~5 tester) └── middleware/context_test.go (~4 tester) Fase 5 (P3): Rendering og hjelpere ├── render/render_test.go (~5 tester) └── handler/api/helpers_test.go (~7 tester) Fase 6: Integrasjonstester └── integration_test.go (~10 tester, build tag) ``` **Totalt**: ~132 nye tester → fra 44 til ~176 testfunksjoner. --- ## Kjøring ```bash # Alle tester go test ./... # Én pakke go test ./internal/store/... # Med dekning go test -cover ./... # Detaljert dekningsrapport go test -coverprofile=coverage.out ./... && go tool cover -html=coverage.out # Kun integrasjonstester (om build tag brukes) go test -tags=integration ./... ```