Gabe Farrell 2 years ago
commit a9cc5a8aad

@ -0,0 +1,11 @@
# syntax=docker/dockerfile:1
FROM golang:1.21
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY *.go ./
COPY ./server/*.go ./server/
COPY ./trivia/*.go ./trivia/
COPY trivia.json ./
RUN CGO_ENABLED=0 GOOS=linux go build -o /TriviaAPI
CMD ["/TriviaAPI"]

@ -0,0 +1,23 @@
Stateless, Containerized Trivia API
---
This project has a blog post going through the development process. Read the post here: https://mnrva.dev/posts/stateless-containerized-trivia-api-go
## Running the server
Copy the repo
```bash
git clone git@github.com:gabehf/trivia-api.git
```
Then, run the server using Go
```bash
cd trivia-api
go run .
```
Or, build the docker image and run that
```bash
docker build --tag trivia-api .
```
Then,
```bash
docker run -p 3000:3000 trivia-api
```

@ -0,0 +1,19 @@
module github.com/gabehf/trivia-api
go 1.21.4
require github.com/labstack/echo/v4 v4.11.3
require (
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
)

@ -0,0 +1,47 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/labstack/echo/v4 v4.11.3 h1:Upyu3olaqSHkCjs1EJJwQ3WId8b8b1hxbogyommKktM=
github.com/labstack/echo/v4 v4.11.3/go.mod h1:UcGuQ8V6ZNRmSweBIJkPvGfwCMIlFmiqrPqiEBfPYws=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

@ -0,0 +1,12 @@
package main
import (
"log"
"github.com/gabehf/trivia-api/server"
)
func main() {
log.Println("Trivia API listening on http://127.0.0.1:3000")
log.Fatal(server.Run())
}

@ -0,0 +1,50 @@
package server
import (
"strings"
"github.com/labstack/echo/v4"
)
type GetGuessRequest struct {
QuestionId string `json:"question_id" query:"question_id"`
Guess string `json:"guess" query:"guess"`
}
type GetGuessResponse struct {
QuestionId string `json:"question_id"`
Correct bool `json:"correct"`
}
func (s *Server) GetGuess(e echo.Context) error {
req := new(GetGuessRequest)
e.Bind(req)
// ensure required parameters exist
errs := make(map[string]string, 0)
if req.Guess == "" {
errs["guess"] = "required parameter missing"
}
if req.QuestionId == "" {
errs["question_id"] = "required parameter missing"
}
if len(errs) > 0 {
return e.JSON(400, &ErrorResponse{
Error: true,
Data: errs,
})
}
question := s.Q.GetQuestionById(req.QuestionId)
if question == nil {
errs["question_id"] = "invalid or malformed"
return e.JSON(404, &ErrorResponse{
Error: true,
Data: errs,
})
}
// validate answer with case insensitive string compare
correct := strings.EqualFold(question.Answer, req.Guess)
return e.JSONPretty(200, &GetGuessResponse{req.QuestionId, correct}, " ")
}

@ -0,0 +1,127 @@
package server_test
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gabehf/trivia-api/server"
"github.com/labstack/echo/v4"
)
func TestGuessHandler(t *testing.T) {
jsonBody := []byte("{\"question_id\":\"World History|0\",\"guess\":\"seven\"}")
// OK path: json body
e := echo.New()
req := httptest.NewRequest("GET", "/guess", bytes.NewReader(jsonBody))
req.Header["Content-Type"] = []string{"application/json"}
res := httptest.NewRecorder()
c := e.NewContext(req, res)
err := S.GetGuess(c)
if err != nil {
t.Errorf("expected nil error, got %v", err)
}
if res.Code != http.StatusOK {
t.Errorf("expected status 200 OK, got %d", res.Code)
}
result := new(server.GetGuessResponse)
err = json.Unmarshal(res.Body.Bytes(), result)
if err != nil {
t.Error("malformed json response")
}
if result.QuestionId != "World History|0" {
t.Errorf("expected question_id 'World History|0', got '%s'", result.QuestionId)
}
if result.Correct != true {
t.Errorf("expected correct to be true, got false")
}
// OK path: urlencoded body
e = echo.New()
req = httptest.NewRequest("GET", "/guess?question_id=World+History%7C0&guess=Seven", nil)
req.Header["Content-Type"] = []string{"application/x-www-form-urlencoded"}
res = httptest.NewRecorder()
c = e.NewContext(req, res)
err = S.GetGuess(c)
if err != nil {
t.Errorf("expected nil error, got %v", err)
}
if res.Code != http.StatusOK {
t.Errorf("expected status 200 OK, got %d", res.Code)
}
result = new(server.GetGuessResponse)
err = json.Unmarshal(res.Body.Bytes(), result)
if err != nil {
t.Error("malformed json response")
}
if result.QuestionId != "World History|0" {
t.Errorf("expected question_id 'World History|0', got '%s'", result.QuestionId)
}
if result.Correct != true {
t.Errorf("expected correct to be true, got false")
}
// FAIL path: invalid question id
e = echo.New()
req = httptest.NewRequest("GET", "/guess?question_id=hey&guess=Seven", nil)
req.Header["Content-Type"] = []string{"application/x-www-form-urlencoded"}
res = httptest.NewRecorder()
c = e.NewContext(req, res)
err = S.GetGuess(c)
if err != nil {
t.Errorf("expected nil error, got %v", err)
}
if res.Code != http.StatusNotFound {
t.Errorf("expected status 400 Bad Request, got %d", res.Code)
}
errResult := struct {
Error bool
Data map[string]string
}{}
err = json.Unmarshal(res.Body.Bytes(), &errResult)
if err != nil {
t.Error("malformed json response")
}
if !errResult.Error {
t.Error("expected error to be true, got false")
}
if errResult.Data["question_id"] == "" {
t.Errorf("expected error information in data[question_id], got \"\"")
}
// FAIL path: missing params
e = echo.New()
req = httptest.NewRequest("GET", "/guess", nil)
req.Header["Content-Type"] = []string{"application/x-www-form-urlencoded"}
res = httptest.NewRecorder()
c = e.NewContext(req, res)
err = S.GetGuess(c)
if err != nil {
t.Errorf("expected nil error, got %v", err)
}
if res.Code != http.StatusBadRequest {
t.Errorf("expected status 400 Bad Request, got %d", res.Code)
}
errResult = struct {
Error bool
Data map[string]string
}{}
err = json.Unmarshal(res.Body.Bytes(), &errResult)
if err != nil {
t.Error("malformed json response")
}
if !errResult.Error {
t.Error("expected error to be true, got false")
}
if errResult.Data["question_id"] == "" {
t.Errorf("expected error information in data[question_id], got \"\"")
}
if errResult.Data["guess"] == "" {
t.Errorf("expected error information in data[guess], got \"\"")
}
}

@ -0,0 +1,71 @@
package server
import (
"math/rand"
"strconv"
"github.com/labstack/echo/v4"
)
type GetTriviaRequest struct {
Category string `json:"category" query:"category"`
}
type GetTriviaResponse struct {
QuestionId string `json:"question_id"`
Question string `json:"question"`
Category string `json:"category"`
Format string `json:"format"`
Choices map[string]string `json:"choices,omitempty"`
}
type ErrorResponse struct {
Error bool `json:"error"`
Data map[string]string `json:"data,omitempty"`
Message string `json:"message,omitempty"`
}
func (s *Server) GetTrivia(e echo.Context) error {
req := new(GetTriviaRequest)
e.Bind(req)
question, qIndex := s.Q.GetRandomQuestion(req.Category)
if question == nil {
return e.JSON(404, &ErrorResponse{
Error: true,
Data: map[string]string{
"category": "category is invalid",
},
})
}
// randomly order answer choices if the format is multiple choice
if question.Format == "MultipleChoice" && question.Choices != nil {
rand.Shuffle(len(question.Choices), func(i, j int) {
question.Choices[i], question.Choices[j] = question.Choices[j], question.Choices[i]
})
// enforce that multiple choice questions must have four choices
// if not, there must be an error in our data somewhere that we need
// to fix
if len(question.Choices) != 4 {
return e.JSON(500, &ErrorResponse{
Error: true,
Message: "internal server error",
})
}
}
// build and return response
tq := new(GetTriviaResponse)
tq.QuestionId = question.Category + "|" + strconv.Itoa(qIndex)
tq.Category = question.Category
tq.Format = question.Format
tq.Question = question.Question
if tq.Format == "MultipleChoice" {
tq.Choices = map[string]string{
"A": question.Choices[0],
"B": question.Choices[1],
"C": question.Choices[2],
"D": question.Choices[3],
}
}
return e.JSONPretty(200, tq, " ")
}

@ -0,0 +1,145 @@
package server_test
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gabehf/trivia-api/server"
"github.com/labstack/echo/v4"
)
func TestTriviaHandler(t *testing.T) {
jsonBody := []byte("{\"category\":\"World History\"}")
expect := server.GetTriviaResponse{
Question: "The ancient city of Rome was built on how many hills?",
Format: "MultipleChoice",
Category: "World History",
}
// OK path: json body
e := echo.New()
req := httptest.NewRequest("GET", "/trivia", bytes.NewReader(jsonBody))
req.Header["Content-Type"] = []string{"application/json"}
res := httptest.NewRecorder()
c := e.NewContext(req, res)
err := S.GetTrivia(c)
if err != nil {
t.Errorf("expected nil error, got %v", err)
}
if res.Code != http.StatusOK {
t.Errorf("expected status 200 OK, got %d", res.Code)
}
result := new(server.GetTriviaResponse)
err = json.Unmarshal(res.Body.Bytes(), result)
if err != nil {
t.Error("malformed json response")
}
if result.Question != expect.Question {
t.Errorf("expected question '%s', got '%s'", expect.Question, result.Question)
}
if result.Format != expect.Format {
t.Errorf("expected format %s, got %s", expect.Format, result.Format)
}
if !strings.EqualFold(expect.Category, result.Category) {
t.Errorf("expected category %s, got %s", expect.Category, result.Category)
}
// OK path: urlencoded body
e = echo.New()
req = httptest.NewRequest("GET", "/trivia?category=World+History", nil)
req.Header["Content-Type"] = []string{"application/x-www-form-urlencoded"}
res = httptest.NewRecorder()
c = e.NewContext(req, res)
err = S.GetTrivia(c)
if err != nil {
t.Errorf("expected nil error, got %v", err)
}
if res.Code != http.StatusOK {
t.Errorf("expected status 200 OK, got %d", res.Code)
}
expect = server.GetTriviaResponse{
Question: "The ancient city of Rome was built on how many hills?",
Format: "MultipleChoice",
Category: "World History",
}
result = new(server.GetTriviaResponse)
err = json.Unmarshal(res.Body.Bytes(), result)
if err != nil {
t.Error("malformed json response")
}
if result.Question != expect.Question {
t.Errorf("expected question '%s', got '%s'", expect.Question, result.Question)
}
if result.Format != expect.Format {
t.Errorf("expected format %s, got %s", expect.Format, result.Format)
}
if !strings.EqualFold(expect.Category, result.Category) {
t.Errorf("expected category %s, got %s", expect.Category, result.Category)
}
// OK path: no body (random category)
e = echo.New()
req = httptest.NewRequest("GET", "/trivia", nil)
res = httptest.NewRecorder()
c = e.NewContext(req, res)
err = S.GetTrivia(c)
if err != nil {
t.Errorf("expected nil error, got %v", err)
}
if res.Code != http.StatusOK {
t.Errorf("expected status 200 OK, got %d", res.Code)
}
expect = server.GetTriviaResponse{
Question: "The ancient city of Rome was built on how many hills?",
Format: "MultipleChoice",
Category: "World History",
}
result = new(server.GetTriviaResponse)
err = json.Unmarshal(res.Body.Bytes(), result)
if err != nil {
t.Error("malformed json response")
}
if result.Question != expect.Question {
t.Errorf("expected question '%s', got '%s'", expect.Question, result.Question)
}
if result.Format != expect.Format {
t.Errorf("expected format %s, got %s", expect.Format, result.Format)
}
if !strings.EqualFold(expect.Category, result.Category) {
t.Errorf("expected category %s, got %s", expect.Category, result.Category)
}
// FAIL path: invalid category
e = echo.New()
req = httptest.NewRequest("GET", "/trivia?category=70s+Music", nil)
req.Header["Content-Type"] = []string{"application/x-www-form-urlencoded"}
res = httptest.NewRecorder()
c = e.NewContext(req, res)
err = S.GetTrivia(c)
if err != nil {
t.Errorf("expected nil error, got %v", err)
}
if res.Code != http.StatusNotFound {
t.Errorf("expected status 404 Not Found, got %d", res.Code)
}
errResult := struct {
Error bool
Data map[string]string
}{}
err = json.Unmarshal(res.Body.Bytes(), &errResult)
if err != nil {
t.Error("malformed json response")
}
if !errResult.Error {
t.Error("expected error to be true, got false")
}
if errResult.Data["category"] == "" {
t.Errorf("expected error information in data[category], got \"\"")
}
}

@ -0,0 +1,44 @@
package server
import (
"os"
"github.com/gabehf/trivia-api/trivia"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
type Server struct {
Q *trivia.Questions
}
func (s *Server) Init() {
s.Q = new(trivia.Questions)
s.Q.Init()
}
func Run() error {
// init server struct
s := new(Server)
s.Init()
// load trivia data
file, err := os.Open("trivia.json")
if err != nil {
panic(err)
}
err = s.Q.Load(file)
if err != nil {
panic(err)
}
// create router and mount handlers
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.GET("/trivia", s.GetTrivia)
e.GET("/guess", s.GetGuess)
// start listening
return e.Start(":3000")
}

@ -0,0 +1,34 @@
package server_test
import (
"testing"
"github.com/gabehf/trivia-api/server"
"github.com/gabehf/trivia-api/trivia"
)
var S *server.Server
func TestMain(m *testing.M) {
S = new(server.Server)
S.Init()
S.Q.Init()
S.Q.Categories = []string{"world history"}
S.Q.M = map[string][]trivia.Question{
"world history": {
{
Question: "The ancient city of Rome was built on how many hills?",
Format: "MultipleChoice",
Category: "World History",
Choices: []string{
"Eight",
"Four",
"Nine",
"Seven",
},
Answer: "Seven",
},
},
}
m.Run()
}

@ -0,0 +1,741 @@
{
"world history": [
{
"category": "World History",
"question": "How many years did the 100 years war last?",
"answer": "116",
"format": "MultipleChoice",
"choices": [
"116",
"87",
"12",
"205"
]
},
{
"category": "World History",
"question": "In which year was John F. Kennedy assassinated?",
"answer": "1963",
"format": "MultipleChoice",
"choices": [
"1963",
"1967",
"1958",
"1978"
]
},
{
"category": "World History",
"question": "Which world leader is famous for his “Little Red Book”?",
"answer": "Mao Zedong",
"format": "MultipleChoice",
"choices": [
"Mao Zedong",
"Xi Xinping",
"Ho Chi Minh",
"Kim Jong-Un"
]
},
{
"category": "World History",
"question": "The ancient city of Rome was built on how many hills?",
"answer": "Seven",
"format": "MultipleChoice",
"choices": [
"Seven",
"Eight",
"Four",
"Twelve"
]
},
{
"category": "World History",
"question": "Before becoming the state of New Jersey, this US state was originally known as New __________.",
"answer": "Netherlands",
"format": "MultipleChoice",
"choices": [
"Denmark",
"Netherlands",
"Spain",
"England"
]
},
{
"category": "World History",
"question": "Which ancient city was famous for its Hanging Gardens?",
"answer": "Babylon",
"format": "MultipleChoice",
"choices": [
"Mesopotamia",
"Athens",
"Babylon",
"Rome"
]
},
{
"category": "World History",
"question": "In which country was Adolf Hitler born?",
"answer": "",
"format": "MultipleChoice",
"choices": [
"Australia",
"Austria",
"Germany",
"Switzerland"
]
},
{
"category": "World History",
"question": "The Hundred Years War was a conflict between the two nations France and who?",
"answer": "England",
"format": "MultipleChoice",
"choices": [
"England",
"Spain",
"Italy",
"China"
]
},
{
"category": "World History",
"question": "Who wrote the document known as the 95 Theses?",
"answer": "Martin Luther",
"format": "MultipleChoice",
"choices": [
"Martin Luther",
"Pope Francis",
"The Dalai Lama",
"Barack Obama"
]
},
{
"category": "World History",
"question": "Victory in Europe Day, or VE Day, celebrates the Allies' victory over Nazi Germany on which date?",
"answer": "May 8th",
"format": "MultipleChoice",
"choices": [
"May 8th",
"May 9th",
"July 4th",
"January 6th"
]
},
{
"category": "World History",
"question": "The Reign of Terror was a period during which major social and political event?",
"answer": "French Revolution",
"format": "MultipleChoice",
"choices": [
"French Revolution",
"Nazi Occupation of Poland",
"November 11th",
"WWI"
]
},
{
"category": "World History",
"question": "Which Chinese destination was the site of protests in 1989?",
"answer": "Tiananmen Square",
"format": "MultipleChoice",
"choices": [
"Tiananmen Square",
"Taiwan",
"Guangzhou",
"Hangzhou"
]
},
{
"category": "World History",
"question": "True or False: Civil rights leader Martin Luther King, Jr won the Nobel Peace prize in 1964.",
"answer": "True",
"format": "TrueFalse"
},
{
"category": "World History",
"question": "True or False: John Wilkes Booth assassinated Abraham Lincoln.",
"answer": "True",
"format": "TrueFalse"
},
{
"category": "World History",
"question": "True or False: Cleopatra, the last Queen of Egypt, was born in Rome",
"answer": "False",
"format": "TrueFalse"
}
],
"art": [
{
"category": "Art",
"question": "Banksy's artwork started appearing on walls in which decade?",
"answer": "1990s",
"format": "MultipleChoice",
"choices": [
"1990s",
"2000s",
"2010s",
"1970s"
]
},
{
"category": "Art",
"question": "Which female Mexican artist is famous for self-portraits?",
"answer": "Frida Kahlo",
"format": "MultipleChoice",
"choices": [
"Leonora Carrington",
"Sofía Táboas",
"Frida Kahlo",
"Elizabeth Catlett"
]
},
{
"category": "Art",
"question": "Renoir was one of the leading artists in which artistic movement?",
"answer": "Impressionism",
"format": "MultipleChoice",
"choices": [
"Impressionism",
"Realism",
"Surrealism",
"New Wave"
]
},
{
"category": "Art",
"question": "Which rebel leader was featured in the Pop Art of Andy Warhol?",
"answer": "Che Guevara",
"format": "MultipleChoice",
"choices": [
"Luke Skywalker",
"Che Guevara",
"Marilyn Monroe",
"Fidel Castro"
]
},
{
"category": "Art",
"question": "Katsushika Hokusai painted The Great _______ off Kanagawa circa 1830.",
"answer": "Wave",
"format": "MultipleChoice",
"choices": [
"Wave",
"Fleet",
"Island",
"Skies"
]
},
{
"category": "Art",
"question": "The Starry Night Over The Rhone was painted in 1888 by which artist?",
"answer": "Van Gogh",
"format": "MultipleChoice",
"choices": [
"Monet",
"Da Vinci",
"Van Gogh",
"Raphael"
]
},
{
"category": "Art",
"question": "Vincent Van Gogh is considered by many to be Post-Impressionist, or \"The Father of ________\".",
"answer": "Expressionism",
"format": "MultipleChoice",
"choices": [
"Expressionism",
"Impressionism",
"Modernism",
"Post-Modernism"
]
},
{
"category": "Art",
"question": "In Botticelli's \"Birth of Venus\" (c. 1485), Venus is standing on a(n) what?",
"answer": "Shell",
"format": "MultipleChoice",
"choices": [
"Shell",
"Wave",
"Rock",
"Whale"
]
},
{
"category": "Art",
"question": "Johannes Vermeer's \"Girl with a Pearl ________\" (c. 1665) shares its name with a 2003 Romance film starring Scarlett Johansson.",
"answer": "Earring",
"format": "MultipleChoice",
"choices": [
"Necklace",
"Earring",
"Hairclip",
"Bracelet"
]
},
{
"category": "Art",
"question": "Alexandros of Antioch's \"Venus de Milo\" (c. 150 BC) is a sculpture depicting which greek goddess?",
"answer": "Aphrodite",
"format": "MultipleChoice",
"choices": [
"Aphrodite",
"Artemis",
"Persephone",
"Hestia"
]
},
{
"category": "Art",
"question": "\"The Night Watch\" is one of the most famous works by which artist?",
"answer": "Rembrandt",
"format": "MultipleChoice",
"choices": [
"Monet",
"Van Gogh",
"Rembrandt",
"Renoir"
]
},
{
"category": "Art",
"question": "True or False: The Smithsonian American Art Museum is in Washington, D.C.",
"answer": "True",
"format": "TrueFalse"
},
{
"category": "Art",
"question": "Which artistic movement includes works like 'Starry Night' by Vincent van Gogh?",
"answer": "Post-Impressionism",
"format": "MultipleChoice",
"choices": ["Cubism", "Surrealism", "Impressionism", "Post-Impressionism"]
},
{
"category": "Art",
"question": "True or False: The painting 'The Persistence of Memory' features melting clocks.",
"answer": "True",
"format": "TrueFalse"
},
{
"category": "Art",
"question": "True or False: Claude Monet and Edgar Degas are both examples of Renaissance artists.",
"answer": "False",
"format": "TrueFalse"
}
],
"movies": [
{
"category": "Movies",
"question": "In which film did Humphrey Bogart say the famous line, 'Here's looking at you, kid'?",
"answer": "Casablanca",
"format": "MultipleChoice",
"choices": ["The Maltese Falcon", "Casablanca", "Key Largo", "To Have and Have Not"]
},
{
"category": "Movies",
"question": "Who directed the 1994 film 'Pulp Fiction'?",
"answer": "Quentin Tarantino",
"format": "MultipleChoice",
"choices": ["Martin Scorsese", "Quentin Tarantino", "Coen Brothers", "Steven Spielberg"]
},
{
"category": "Movies",
"question": "Which animated film features a character named Simba?",
"answer": "The Lion King",
"format": "MultipleChoice",
"choices": ["Aladdin", "Beauty and the Beast", "The Lion King", "Toy Story"]
},
{
"category": "Movies",
"question": "In 'The Shawshank Redemption,' what does Andy Dufresne carve his way through to escape from prison?",
"answer": "Wall",
"format": "MultipleChoice",
"choices": ["Floor", "Wall", "Ceiling", "Cell Bars"]
},
{
"category": "Movies",
"question": "Which actor played the role of Tony Stark in the Marvel Cinematic Universe?",
"answer": "Robert Downey Jr.",
"format": "MultipleChoice",
"choices": ["Chris Evans", "Mark Ruffalo", "Chris Hemsworth", "Robert Downey Jr."]
},
{
"category": "Movies",
"question": "True or False: 'The Godfather' is based on a novel of the same name by Mario Puzo.",
"answer": "True",
"format": "TrueFalse"
},
{
"category": "Movies",
"question": "Who won the Academy Award for Best Actress for her role in 'La La Land'?",
"answer": "Emma Stone",
"format": "MultipleChoice",
"choices": ["Emma Stone", "Meryl Streep", "Natalie Portman", "Jennifer Lawrence"]
},
{
"category": "Movies",
"question": "Which film features a character named Travis Bickle who drives a taxi in New York City?",
"answer": "Taxi Driver",
"format": "MultipleChoice",
"choices": ["Collateral", "Nightcrawler", "Drive", "Taxi Driver"]
},
{
"category": "Movies",
"question": "What is the name of the fictional African country in 'Black Panther'?",
"answer": "Wakanda",
"format": "MultipleChoice",
"choices": ["Zamunda", "Wakanda", "Genovia", "Elbonia"]
},
{
"category": "Movies",
"question": "True or False: 'Forrest Gump' is based on a novel by Winston Groom.",
"answer": "True",
"format": "TrueFalse"
},
{
"category": "Movies",
"question": "Who directed the 2017 horror film 'Get Out'?",
"answer": "Jordan Peele",
"format": "MultipleChoice",
"choices": ["Jordan Peele", "Ari Aster", "John Krasinski", "James Wan"]
},
{
"category": "Movies",
"question": "Which James Cameron film features the line, 'I'll be back'?",
"answer": "The Terminator",
"format": "MultipleChoice",
"choices": ["Aliens", "The Terminator", "Avatar", "Titanic"]
},
{
"category": "Movies",
"question": "Who played the lead role in the film 'The Matrix'?",
"answer": "Keanu Reeves",
"format": "MultipleChoice",
"choices": ["Keanu Reeves", "Tom Cruise", "Will Smith", "Brad Pitt"]
},
{
"category": "Movies",
"question": "True or False: 'The Dark Knight' was directed by Christopher Nolan.",
"answer": "True",
"format": "TrueFalse"
},
{
"category": "Movies",
"question": "In the film 'Inception,' what is used to enter the dream world?",
"answer": "Dream Machine",
"format": "MultipleChoice",
"choices": ["Dreamcatcher", "Dream Weaver", "Dream Machine", "Dream Portal"]
}
],
"music": [
{
"category": "Music",
"question": "Which band released the album 'The Dark Side of the Moon' in 1973?",
"answer": "Pink Floyd",
"format": "MultipleChoice",
"choices": ["Led Zeppelin", "The Beatles", "Pink Floyd", "The Rolling Stones"]
},
{
"category": "Music",
"question": "Who is known as the 'King of Pop'?",
"answer": "Michael Jackson",
"format": "MultipleChoice",
"choices": ["Elvis Presley", "Michael Jackson", "Prince", "David Bowie"]
},
{
"category": "Music",
"question": "Which female artist released the album '21' in 2011?",
"answer": "Adele",
"format": "MultipleChoice",
"choices": ["Taylor Swift", "Adele", "Beyoncé", "Rihanna"]
},
{
"category": "Music",
"question": "In which year did The Beatles release their debut album 'Please Please Me'?",
"answer": "1963",
"format": "MultipleChoice",
"choices": ["1962", "1963", "1964", "1965"]
},
{
"category": "Music",
"question": "Who is the lead singer of the rock band U2?",
"answer": "Bono",
"format": "MultipleChoice",
"choices": ["Chris Martin", "Bono", "Mick Jagger", "Bruce Springsteen"]
},
{
"category": "Music",
"question": "True or False: Elvis Presley is also known as 'The Boss.'",
"answer": "False",
"format": "TrueFalse"
},
{
"category": "Music",
"question": "Which artist won the Nobel Prize in Literature in 2016?",
"answer": "Bob Dylan",
"format": "MultipleChoice",
"choices": ["Bob Dylan", "Joni Mitchell", "Leonard Cohen", "Paul Simon"]
},
{
"category": "Music",
"question": "In which year did Nirvana release their iconic album 'Nevermind'?",
"answer": "1991",
"format": "MultipleChoice",
"choices": ["1990", "1991", "1992", "1993"]
},
{
"category": "Music",
"question": "Which headline performers took the stage at the halftime show of Super Bowl LIV in 2020?",
"answer": "Shakira and Jennifer Lopez",
"format": "MultipleChoice",
"choices": ["Coldplay", "Shakira and Jennifer Lopez", "Maroon 5", "U2"]
},
{
"category": "Music",
"question": "True or False: Madonna's real name is Madonna.",
"answer": "False",
"format": "TrueFalse"
},
{
"category": "Music",
"question": "Which 1970's pop wrote the 1974 ballad 'Don't Let the Sun Go Down on Me'?",
"answer": "Elton John",
"format": "MultipleChoice",
"choices": ["David Bowie", "Elton John", "Freddie Mercury", "Billy Joel"]
},
{
"category": "Music",
"question": "Which pop star is known as the 'Material Girl'?",
"answer": "Madonna",
"format": "MultipleChoice",
"choices": ["Britney Spears", "Lady Gaga", "Madonna", "Katy Perry"]
},
{
"category": "Music",
"question": "Who wrote the song 'Bohemian Rhapsody'?",
"answer": "Freddie Mercury",
"format": "MultipleChoice",
"choices": ["Freddie Mercury", "John Lennon", "David Bowie", "Elton John"]
},
{
"category": "Music",
"question": "True or False: Adele's full name is Adele Laurie Blue Adkins.",
"answer": "True",
"format": "TrueFalse"
},
{
"category": "Music",
"question": "Which country is the birthplace of the band ABBA?",
"answer": "Sweden",
"format": "MultipleChoice",
"choices": ["Norway", "Denmark", "Sweden", "Finland"]
}
],
"geography": [
{
"category": "Geography",
"question": "What is the capital city of Japan?",
"answer": "Tokyo",
"format": "MultipleChoice",
"choices": ["Beijing", "Seoul", "Bangkok", "Tokyo"]
},
{
"category": "Geography",
"question": "Which river is the longest in the world?",
"answer": "Nile",
"format": "MultipleChoice",
"choices": ["Amazon", "Nile", "Yangtze", "Mississippi"]
},
{
"category": "Geography",
"question": "The __________ Desert is the largest desert in the world?",
"answer": "Antarctic",
"format": "MultipleChoice",
"choices": ["Sahara", "Gobi", "Arabian", "Antarctic"]
},
{
"category": "Geography",
"question": "Which country is known as the 'Land of the Rising Sun'?",
"answer": "Japan",
"format": "MultipleChoice",
"choices": ["China", "South Korea", "Japan", "Vietnam"]
},
{
"category": "Geography",
"question": "What is the capital city of Australia?",
"answer": "Canberra",
"format": "MultipleChoice",
"choices": ["Sydney", "Melbourne", "Canberra", "Brisbane"]
},
{
"category": "Geography",
"question": "True or False: The Amazon Rainforest is located in Africa.",
"answer": "False",
"format": "TrueFalse"
},
{
"category": "Geography",
"question": "Which mountain is the tallest in North America?",
"answer": "Denali",
"format": "MultipleChoice",
"choices": ["Mount McKinley", "Mount Logan", "Denali", "Mount St. Elias"]
},
{
"category": "Geography",
"question": "In which continent is the Sahara Desert located?",
"answer": "Africa",
"format": "MultipleChoice",
"choices": ["Asia", "Africa", "South America", "Australia"]
},
{
"category": "Geography",
"question": "Which ocean is the largest on Earth?",
"answer": "Pacific Ocean",
"format": "MultipleChoice",
"choices": ["Atlantic Ocean", "Indian Ocean", "Southern Ocean", "Pacific Ocean"]
},
{
"category": "Geography",
"question": "True or False: The Great Wall of China is visible from space without aid.",
"answer": "False",
"format": "TrueFalse"
},
{
"category": "Geography",
"question": "Which river flows through Cairo?",
"answer": "Nile",
"format": "MultipleChoice",
"choices": ["Tigris", "Euphrates", "Nile", "Jordan"]
},
{
"category": "Geography",
"question": "What is the smallest country in the world?",
"answer": "Vatican City",
"format": "MultipleChoice",
"choices": ["Monaco", "San Marino", "Liechtenstein", "Vatican City"]
},
{
"category": "Geography",
"question": "Which African country is known as the 'Pearl of Africa'?",
"answer": "Uganda",
"format": "MultipleChoice",
"choices": ["Kenya", "Tanzania", "Uganda", "Rwanda"]
},
{
"category": "Geography",
"question": "True or False: Mount Everest is located in the Himalayas.",
"answer": "True",
"format": "TrueFalse"
},
{
"category": "Geography",
"question": "Which strait separates Europe and Asia?",
"answer": "Bosporus",
"format": "MultipleChoice",
"choices": ["Strait of Gibraltar", "Bering Strait", "Bosporus", "Strait of Hormuz"]
}
],
"science": [
{
"category": "Science",
"question": "What is the chemical symbol for the element gold?",
"answer": "Au",
"format": "MultipleChoice",
"choices": ["Ag", "Fe", "Au", "Cu"]
},
{
"category": "Science",
"question": "Which planet is known as the 'Red Planet'?",
"answer": "Mars",
"format": "MultipleChoice",
"choices": ["Venus", "Mars", "Jupiter", "Saturn"]
},
{
"category": "Science",
"question": "What is the powerhouse of the cell?",
"answer": "Mitochondria",
"format": "MultipleChoice",
"choices": ["Nucleus", "Mitochondria", "Endoplasmic Reticulum", "Golgi Apparatus"]
},
{
"category": "Science",
"question": "What is the largest mammal in the world?",
"answer": "Blue Whale",
"format": "MultipleChoice",
"choices": ["Elephant", "Giraffe", "Blue Whale", "Hippopotamus"]
},
{
"category": "Science",
"question": "Which gas is primarily responsible for the Earth's greenhouse effect?",
"answer": "Carbon Dioxide",
"format": "MultipleChoice",
"choices": ["Oxygen", "Nitrogen", "Carbon Dioxide", "Methane"]
},
{
"category": "Science",
"question": "True or False: Water boils at 100 degrees Celsius at sea level.",
"answer": "True",
"format": "TrueFalse"
},
{
"category": "Science",
"question": "What is the chemical symbol for the element with the atomic number 1?",
"answer": "H",
"format": "MultipleChoice",
"choices": ["He", "H", "Li", "Be"]
},
{
"category": "Science",
"question": "Which scientist developed the theory of general relativity?",
"answer": "Albert Einstein",
"format": "MultipleChoice",
"choices": ["Isaac Newton", "Galileo Galilei", "Marie Curie", "Albert Einstein"]
},
{
"category": "Science",
"question": "What is the chemical symbol for water?",
"answer": "H2O",
"format": "MultipleChoice",
"choices": ["CO2", "O2", "H2O", "N2"]
},
{
"category": "Science",
"question": "True or False: An adult human skeleton has 206 bones.",
"answer": "True",
"format": "TrueFalse"
},
{
"category": "Science",
"question": "Which planet was referred to as the 'Pale Blue Dot'?",
"answer": "Earth",
"format": "MultipleChoice",
"choices": ["Mars", "Venus", "Earth", "Neptune"]
},
{
"category": "Science",
"question": "What is the smallest unit of matter?",
"answer": "Atom",
"format": "MultipleChoice",
"choices": ["Molecule", "Cell", "Atom", "Proton"]
},
{
"category": "Science",
"question": "Which gas do plants absorb from the atmosphere during photosynthesis?",
"answer": "Carbon Dioxide",
"format": "MultipleChoice",
"choices": ["Oxygen", "Methane", "Carbon Monoxide", "Carbon Dioxide"]
},
{
"category": "Science",
"question": "True or False: Antibiotics are effective against viruses.",
"answer": "False",
"format": "TrueFalse"
},
{
"category": "Science",
"question": "What is the largest planet in our solar system?",
"answer": "Jupiter",
"format": "MultipleChoice",
"choices": ["Mars", "Saturn", "Jupiter", "Neptune"]
}
]
}

@ -0,0 +1,109 @@
package trivia
import (
"encoding/json"
"io"
"math/rand"
"strconv"
"strings"
"sync"
)
// represents the structure of trivia questions stored
// in the trivia.json file
type Question struct {
Question string `json:"question"`
Category string `json:"category"`
Format string `json:"format"`
Choices []string `json:"choices"`
Answer string `json:"answer"`
}
type Questions struct {
Categories []string
M map[string][]Question
lock *sync.RWMutex
}
func (q *Questions) Init() {
q.lock = &sync.RWMutex{}
}
func (q *Questions) Load(r io.Reader) error {
q.lock.Lock()
defer q.lock.Unlock()
if q.M == nil {
q.M = make(map[string][]Question, 0)
}
err := json.NewDecoder(r).Decode(&q.M)
if err != nil {
return err
}
if q.Categories == nil {
q.Categories = make([]string, 0)
}
for key := range q.M {
q.Categories = append(q.Categories, key)
}
return nil
}
func (q *Questions) categoryExists(cat string) bool {
q.lock.RLock()
defer q.lock.RUnlock()
cat = strings.ToLower(cat)
if q.M[cat] == nil || len(q.M[cat]) < 1 {
return false
}
return true
}
func (q *Questions) getRandomCategory() string {
q.lock.RLock()
defer q.lock.RUnlock()
return q.Categories[rand.Int()%len(q.Categories)]
}
// Gets a random question from the category, if specified.
func (q *Questions) GetRandomQuestion(category string) (*Question, int) {
q.lock.RLock()
defer q.lock.RUnlock()
// NOTE: it is okay to call another function that locks the RWMutex here,
// as it will not cause a deadlock since CategoryExists only locks the Read
if category == "" {
category = q.getRandomCategory()
} else if !q.categoryExists(category) {
return nil, 0
}
category = strings.ToLower(category)
qIndex := rand.Int() % len(q.M[category])
return &q.M[category][qIndex], qIndex
}
func (q *Questions) GetQuestionById(id string) *Question {
// get values from question_id
questionSlice := strings.Split(id, "|")
if len(questionSlice) != 2 {
return nil
}
category, indexS := questionSlice[0], questionSlice[1]
category = strings.ToLower(category)
index, err := strconv.Atoi(indexS)
if err != nil {
return nil
}
q.lock.RLock()
defer q.lock.RUnlock()
// ensure category exists
if !q.categoryExists(category) {
return nil
}
// ensure question index is valid
if len(q.M[category]) <= index {
return nil
}
// retrieve question
return &q.M[category][index]
}

@ -0,0 +1,163 @@
package trivia_test
import (
"bytes"
"reflect"
"slices"
"testing"
"github.com/gabehf/trivia-api/trivia"
)
func TestGetRandomQuestion(t *testing.T) {
// on OK path, GetTrivia must return the question in our test data
tq, _ := Q.GetRandomQuestion("world history")
if tq == nil {
t.Fatal("trivia question must not be nil")
}
if !reflect.DeepEqual(tq, expect) {
t.Errorf("returned question does not match expectation, got %v", tq)
}
// with no category specified, GetTrivia must pick a random category and fetch a question
// with only one question in our test data, it is the same question from before
tq, _ = Q.GetRandomQuestion("")
if tq == nil {
t.Fatal("trivia question must not be nil")
}
if !reflect.DeepEqual(tq, expect) {
t.Errorf("returned question does not match expectation, got %v", tq)
}
// on FAIL path, GetTrivia must return nil to indicate no questions are found
tq, _ = Q.GetRandomQuestion("Geography")
if tq != nil {
t.Errorf("expected nil, got %v", tq)
}
}
func TestGetQuestionById(t *testing.T) {
// on OK path, GetTrivia must return the question in our test data
tq := Q.GetQuestionById("world History|0")
if tq == nil {
t.Fatal("trivia question must not be nil")
}
if !reflect.DeepEqual(tq, expect) {
t.Errorf("returned question does not match expectation, got %v", tq)
}
// FAIL path: malformed id
tq = Q.GetQuestionById("hey")
if tq != nil {
t.Errorf("expected nil, got %v", tq)
}
// FAIL path: invalid category
tq = Q.GetQuestionById("hey|0")
if tq != nil {
t.Errorf("expected nil, got %v", tq)
}
// FAIL path: invalid index
tq = Q.GetQuestionById("world history|9")
if tq != nil {
t.Errorf("expected nil, got %v", tq)
}
}
func TestLoad(t *testing.T) {
json := []byte(`
{
"world history": [
{
"category": "World History",
"question": "How many years did the 100 years war last?",
"answer": "116",
"format": "MultipleChoice",
"choices": [
"116",
"87",
"12",
"205"
]
},
{
"category": "World History",
"question": "True or False: John Wilkes Booth assassinated Abraham Lincoln.",
"answer": "True",
"format": "TrueFalse"
}
],
"geography": [
{
"category": "Geography",
"question": "What is the capital city of Japan?",
"answer": "Tokyo",
"format": "MultipleChoice",
"choices": [
"Beijing",
"Seoul",
"Bangkok",
"Tokyo"
]
},
{
"category": "Geography",
"question": "True or False: The Amazon Rainforest is located in Africa.",
"answer": "False",
"format": "TrueFalse"
}
]
}
`)
expectCategories := []string{"world history", "geography"}
expectQuestions := map[string][]trivia.Question{
"world history": {
{
Question: "How many years did the 100 years war last?",
Format: "MultipleChoice",
Answer: "116",
Category: "World History",
Choices: []string{
"116",
"87",
"12",
"205",
},
},
{
Question: "True or False: John Wilkes Booth assassinated Abraham Lincoln.",
Format: "TrueFalse",
Answer: "True",
Category: "World History",
},
},
"geography": {
{
Question: "What is the capital city of Japan?",
Format: "MultipleChoice",
Answer: "Tokyo",
Category: "Geography",
Choices: []string{"Beijing", "Seoul", "Bangkok", "Tokyo"},
},
{
Question: "True or False: The Amazon Rainforest is located in Africa.",
Format: "TrueFalse",
Answer: "False",
Category: "Geography",
},
},
}
qq := new(trivia.Questions)
qq.Init()
err := qq.Load(bytes.NewReader(json))
if err != nil {
t.Errorf("expected error to be nil, got %v", err)
}
for _, cat := range expectCategories {
if !slices.Contains(qq.Categories, cat) {
t.Errorf("expected category %s not present", cat)
}
}
if !reflect.DeepEqual(qq.M, expectQuestions) {
t.Errorf("unexpected question map, got %v", qq.M)
}
}

@ -0,0 +1,44 @@
package trivia_test
import (
"testing"
"github.com/gabehf/trivia-api/trivia"
)
var Q trivia.Questions
var expect *trivia.Question
func TestMain(m *testing.M) {
Q.Init()
Q.Categories = []string{"world history"}
Q.M = map[string][]trivia.Question{
"world history": {
{
Question: "The ancient city of Rome was built on how many hills?",
Format: "MultipleChoice",
Category: "World History",
Choices: []string{
"Eight",
"Four",
"Nine",
"Seven",
},
Answer: "Seven",
},
},
}
expect = &trivia.Question{
Question: "The ancient city of Rome was built on how many hills?",
Category: "World History",
Format: "MultipleChoice",
Choices: []string{
"Eight",
"Four",
"Nine",
"Seven",
},
Answer: "Seven",
}
m.Run()
}
Loading…
Cancel
Save