Assignment 4: tests, interface refactor, e2e suite
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
# Snippet Sharing API
|
||||
|
||||
A Go + PostgreSQL REST API for sharing code snippets (Assignment 3), with a full test suite (Assignment 4).
|
||||
|
||||
**Name:** Zagar Nashrullah
|
||||
**NRP:** 5803025048
|
||||
|
||||
Module: `5803025048/W04`
|
||||
|
||||
## Run the server
|
||||
|
||||
Set the database variables, then start the server. It listens on `PORT` (default `8080`).
|
||||
|
||||
```powershell
|
||||
$env:DB_HOST="..."; $env:DB_PORT="..."
|
||||
$env:DB_USER="..."; $env:DB_PASSWORD="..."; $env:DB_NAME="..."
|
||||
go run ./cmd/server/main.go
|
||||
```
|
||||
|
||||
`PORT` is the HTTP port of the API. `DB_PORT` is the PostgreSQL port.
|
||||
|
||||
## Run the tests
|
||||
|
||||
Each layer has its own command:
|
||||
|
||||
```
|
||||
go test ./internal/auth/... # unit tests
|
||||
go test ./internal/middleware/... # unit tests, hand-written fake
|
||||
go test ./internal/handler/... # unit tests, hand-written fakes
|
||||
go test ./internal/store/... # integration tests, real PostgreSQL
|
||||
go test ./e2e/... # black-box tests over HTTP
|
||||
```
|
||||
|
||||
What each layer needs:
|
||||
|
||||
- **auth, middleware, handler:** nothing.
|
||||
- **store:** the `DB_*` variables, and `DB_NAME` **must end in `_test`**.
|
||||
- **e2e:** a running server. It uses `API_BASE_URL` (default `http://localhost:8080`).
|
||||
|
||||
To run everything the way the grader does (start the server first, because `./...` includes e2e):
|
||||
|
||||
```
|
||||
go vet ./...
|
||||
go test -race -shuffle=on -count=2 ./...
|
||||
go test -coverpkg=./internal/... -coverprofile=cover.out ./internal/...
|
||||
```
|
||||
|
||||
### Warning: store tests wipe the database
|
||||
|
||||
The store tests `TRUNCATE` all tables at the start of every test. For safety, `TestMain` in `internal/store` refuses to run unless `DB_NAME` ends in `_test` (for example `w04_5803025048_test`). Never point them at a real database.
|
||||
|
||||
If the `DB_*` variables are missing or the database is unreachable, the tests fail. They never skip.
|
||||
|
||||
The e2e tests create their own unique usernames, so they work against a server that already has data.
|
||||
|
||||
## Bugs My Tests Found
|
||||
|
||||
Defects in my Assignment 3 code that these tests exposed, and how I fixed them:
|
||||
|
||||
1. **Errors were not JSON.** Every 400/401/403/404/409/500 used `http.Error`, which sends `text/plain`.
|
||||
*Fix:* a JSON error writer in the handlers and in the middleware.
|
||||
2. **A token without the `Bearer` scheme was accepted.** The middleware and logout used the whole header as the token.
|
||||
*Fix:* one strict `middleware.BearerToken` helper. A missing scheme, `Basic ...`, or an empty token now gives 401.
|
||||
3. **Logout returned 204 for tokens that did not exist.**
|
||||
*Fix:* logout checks that the session exists and returns 401 otherwise.
|
||||
4. **"Not found" leaked the database driver.** Handlers compared against `pgx.ErrNoRows`, and `SnippetStore.Delete` returned `nil` for an unknown id.
|
||||
*Fix:* added `store.ErrNotFound`, returned by every lookup. `Delete` returns it when no row was affected.
|
||||
5. **Passwords longer than 72 bytes caused a 500 on `/register`** (bcrypt rejects them).
|
||||
*Fix:* they now return 400.
|
||||
6. **Username length was counted in bytes, not characters.**
|
||||
*Fix:* it is now counted in runes.
|
||||
7. **`GET /snippets` could return `null` for an empty list.**
|
||||
*Fix:* the handler always returns `[]`, even if the store returns a nil slice.
|
||||
8. **Any login lookup error was reported as 401.**
|
||||
*Fix:* a real database failure is now 500. Only "user not found" and "wrong password" give the (identical) 401.
|
||||
|
||||
## Refactor for testability
|
||||
|
||||
No behavior change. Handlers depend on the `UserStore`, `SessionStore` and `SnippetStore` interfaces (package `handler`). The middleware depends on a small `SessionLookup` interface. The concrete types in `internal/store` satisfy them without importing `handler` or `middleware`.
|
||||
Reference in New Issue
Block a user