80 lines
3.5 KiB
Markdown
80 lines
3.5 KiB
Markdown
# 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`.
|