mirror of
https://github.com/gabehf/trivia-api.git
synced 2026-03-10 16:00:30 -07:00
First
This commit is contained in:
commit
a9cc5a8aad
15 changed files with 1640 additions and 0 deletions
109
trivia/questions.go
Normal file
109
trivia/questions.go
Normal file
|
|
@ -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]
|
||||
}
|
||||
163
trivia/questions_test.go
Normal file
163
trivia/questions_test.go
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
44
trivia/trivia_main_test.go
Normal file
44
trivia/trivia_main_test.go
Normal file
|
|
@ -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…
Add table
Add a link
Reference in a new issue