159 lines
6.6 KiB
Go
159 lines
6.6 KiB
Go
package quizzes_test
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.golem15.com/golem15/sm-quizzes-plugin/models"
|
|
"git.golem15.com/golem15/summercms/modules/bouncer"
|
|
"git.golem15.com/golem15/summercms/modules/surf"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
const adminPrefix = "/backend/api/v1"
|
|
|
|
// adminToken creates a backend user whose role grants permissions and
|
|
// signs them in through the admin API.
|
|
func adminToken(t *testing.T, gdb *gorm.DB, h http.Handler, permissions ...string) string {
|
|
t.Helper()
|
|
suffix := fmt.Sprint(time.Now().UnixNano())
|
|
login, password := "quizzes-"+suffix, "secret-"+suffix
|
|
hash, err := bouncer.HashPassword(4, password)
|
|
must(t, err)
|
|
grants := map[string]int{}
|
|
for _, p := range permissions {
|
|
grants[p] = 1
|
|
}
|
|
raw, _ := json.Marshal(grants)
|
|
var roleID uint
|
|
must(t, gdb.Raw(`INSERT INTO backend_user_roles (name, code, permissions, is_system, created_at, updated_at) VALUES (?, ?, ?, FALSE, NOW(), NOW()) RETURNING id`,
|
|
"Role "+suffix, "role-"+suffix, string(raw)).Scan(&roleID).Error)
|
|
must(t, gdb.Exec(`INSERT INTO backend_users (login, email, password, is_activated, is_superuser, role_id, created_at, updated_at) VALUES (?, ?, ?, TRUE, FALSE, ?, NOW(), NOW())`,
|
|
login, login+"@example.test", hash, roleID).Error)
|
|
body, _ := json.Marshal(map[string]string{"login": login, "password": password})
|
|
req := httptest.NewRequest(http.MethodPost, adminPrefix+"/auth/login", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec := httptest.NewRecorder()
|
|
h.ServeHTTP(rec, req)
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("admin login = %d %s", rec.Code, rec.Body.String())
|
|
}
|
|
var out struct {
|
|
Data struct {
|
|
AccessToken string `json:"access_token"`
|
|
} `json:"data"`
|
|
}
|
|
must(t, json.Unmarshal(rec.Body.Bytes(), &out))
|
|
return out.Data.AccessToken
|
|
}
|
|
|
|
func adminSend(t *testing.T, h http.Handler, method, path, token string, body any) (int, string) {
|
|
t.Helper()
|
|
var reader *bytes.Reader
|
|
if body != nil {
|
|
raw, _ := json.Marshal(body)
|
|
reader = bytes.NewReader(raw)
|
|
} else {
|
|
reader = bytes.NewReader(nil)
|
|
}
|
|
req := httptest.NewRequest(method, adminPrefix+path, reader)
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
req.Header.Set("Accept-Language", "pl")
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec := httptest.NewRecorder()
|
|
h.ServeHTTP(rec, req)
|
|
return rec.Code, rec.Body.String()
|
|
}
|
|
|
|
func TestAdminAgainstDatabase(t *testing.T) {
|
|
app, plugins, gdb := migrated(t)
|
|
h, err := surf.Assemble(app, plugins)
|
|
must(t, err)
|
|
f := seedQuiz(t, gdb, "admin", nil)
|
|
other := seedQuiz(t, gdb, "drugi", func(q *models.Quiz) { q.Title = "Drugi" })
|
|
must(t, gdb.Model(&models.Question{}).Where("id = ?", other.selfQ.ID).Update("category", "Teoria stosunków").Error)
|
|
u, _ := newUser(t, gdb, "ala@example.com")
|
|
must(t, gdb.Create(&models.Attempt{QuizID: f.quiz.ID, UserID: u.ID, StartedAt: time.Now()}).Error)
|
|
|
|
editor := adminToken(t, gdb, h, "golem15.quizzes.access_quizzes")
|
|
viewer := adminToken(t, gdb, h, "golem15.quizzes.access_attempts")
|
|
|
|
// Questions: filter by quiz and by a category with spaces and diacritics.
|
|
code, body := adminSend(t, h, "GET", fmt.Sprintf("/golem15/quizzes/questions?filter[quiz]=%d", f.quiz.ID), editor, nil)
|
|
if code != 200 || strings.Count(body, `"quiz_id"`) > 0 && !strings.Contains(body, "Stolica Polski") {
|
|
t.Fatalf("questions filtered by quiz = %d %s", code, body)
|
|
}
|
|
var list struct {
|
|
Data []map[string]any `json:"data"`
|
|
}
|
|
must(t, json.Unmarshal([]byte(body), &list))
|
|
if len(list.Data) != 3 {
|
|
t.Errorf("questions of one quiz = %d rows: %s", len(list.Data), body)
|
|
}
|
|
code, body = adminSend(t, h, "GET", "/golem15/quizzes/questions/filters/category/options", editor, nil)
|
|
if code != 200 || !strings.Contains(body, "Teoria stosunków") {
|
|
t.Fatalf("category options = %d %s", code, body)
|
|
}
|
|
enc := models.EncodeCategoryValue("Teoria stosunków")
|
|
code, body = adminSend(t, h, "GET", "/golem15/quizzes/questions?filter[category]="+url.QueryEscape(enc), editor, nil)
|
|
must(t, json.Unmarshal([]byte(body), &list))
|
|
if code != 200 || len(list.Data) != 1 {
|
|
t.Errorf("questions in 'Teoria stosunków' = %d %s", code, body)
|
|
}
|
|
code, body = adminSend(t, h, "GET", "/golem15/quizzes/questions/filters/quiz/options", editor, nil)
|
|
if code != 200 || !strings.Contains(body, "Drugi") {
|
|
t.Errorf("quiz options = %d %s", code, body)
|
|
}
|
|
code, body = adminSend(t, h, "GET", fmt.Sprintf("/golem15/quizzes/answers?filter[quiz]=%d", other.quiz.ID), editor, nil)
|
|
must(t, json.Unmarshal([]byte(body), &list))
|
|
if code != 200 || len(list.Data) != 5 {
|
|
t.Errorf("answers of one quiz = %d %s", code, body)
|
|
}
|
|
|
|
// Creating a quiz fills the slug from the title; a duplicate is a 422.
|
|
code, body = adminSend(t, h, "POST", "/golem15/quizzes/quizzes", editor, map[string]any{"title": "Nowy Quiz Łódź", "passing_score": 60})
|
|
if code != 201 && code != 200 {
|
|
t.Fatalf("create quiz = %d %s", code, body)
|
|
}
|
|
var q models.Quiz
|
|
must(t, gdb.Where("title = ?", "Nowy Quiz Łódź").Take(&q).Error)
|
|
if q.Slug != "nowy-quiz-lodz" || q.PassingScore != 60 {
|
|
t.Errorf("created quiz = %+v", q)
|
|
}
|
|
code, body = adminSend(t, h, "POST", "/golem15/quizzes/quizzes", editor, map[string]any{"title": "Nowy quiz łódź", "passing_score": 60})
|
|
if code != 422 || !strings.Contains(body, "slug") {
|
|
t.Errorf("duplicate slug = %d %s", code, body)
|
|
}
|
|
code, body = adminSend(t, h, "POST", "/golem15/quizzes/questions", editor, map[string]any{"quiz": q.ID, "text": "Q?", "type": "essay", "points": 1})
|
|
if code != 422 || !strings.Contains(body, "type") {
|
|
t.Errorf("question with an unknown type = %d %s", code, body)
|
|
}
|
|
|
|
// Attempts are read-only and need their own permission.
|
|
if code, _ := adminSend(t, h, "GET", "/golem15/quizzes/attempts", editor, nil); code != 403 {
|
|
t.Errorf("attempts for a quiz editor = %d, want 403", code)
|
|
}
|
|
code, body = adminSend(t, h, "GET", "/golem15/quizzes/attempts", viewer, nil)
|
|
if code != 200 || !strings.Contains(body, "ala@example.com") {
|
|
t.Errorf("attempts list = %d %s", code, body)
|
|
}
|
|
code, body = adminSend(t, h, "GET", fmt.Sprintf("/golem15/quizzes/attempts?filter[quiz]=%d", other.quiz.ID), viewer, nil)
|
|
must(t, json.Unmarshal([]byte(body), &list))
|
|
if code != 200 || len(list.Data) != 0 {
|
|
t.Errorf("attempts of a quiz without attempts = %d %s", code, body)
|
|
}
|
|
if code, body := adminSend(t, h, "POST", "/golem15/quizzes/attempts", viewer, map[string]any{}); code == 200 || code == 201 {
|
|
t.Errorf("creating an attempt through the admin = %d %s", code, body)
|
|
}
|
|
if code, _ := adminSend(t, h, "GET", "/golem15/quizzes/quizzes", viewer, nil); code != 403 {
|
|
t.Errorf("quizzes for an attempts viewer = %d, want 403", code)
|
|
}
|
|
}
|