generated from VLADIMIR/template
add questions
This commit is contained in:
@@ -295,6 +295,75 @@ func (s *ScenarioService) UpdateScenarioIntro(
|
||||
return s.updateStory(ctx, id, story)
|
||||
}
|
||||
|
||||
// AddScenarioQuestion добавляет вопрос дела в конец списка вопросов сценария.
|
||||
func (s *ScenarioService) AddScenarioQuestion(
|
||||
ctx context.Context,
|
||||
id int,
|
||||
question *storytelling.Question,
|
||||
actorId int,
|
||||
isAdmin bool,
|
||||
) error {
|
||||
if _, err := s.getScenarioForChange(ctx, id, actorId, isAdmin); err != nil {
|
||||
return err
|
||||
}
|
||||
story, err := s.getStory(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
story.Questions = append(story.Questions, question)
|
||||
return s.updateStory(ctx, id, story)
|
||||
}
|
||||
|
||||
// UpdateScenarioQuestion обновляет вопрос дела по его коду. Смена кода
|
||||
// оставляет строки team_answers старого кода в БД
|
||||
func (s *ScenarioService) UpdateScenarioQuestion(
|
||||
ctx context.Context,
|
||||
id int,
|
||||
code string,
|
||||
question *storytelling.Question,
|
||||
actorId int,
|
||||
isAdmin bool,
|
||||
) error {
|
||||
if _, err := s.getScenarioForChange(ctx, id, actorId, isAdmin); err != nil {
|
||||
return err
|
||||
}
|
||||
story, err := s.getStory(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range story.Questions {
|
||||
if story.Questions[i].Code == code {
|
||||
story.Questions[i] = question
|
||||
break
|
||||
}
|
||||
}
|
||||
return s.updateStory(ctx, id, story)
|
||||
}
|
||||
|
||||
// DeleteScenarioQuestion удаляет вопрос дела по его коду.
|
||||
func (s *ScenarioService) DeleteScenarioQuestion(
|
||||
ctx context.Context,
|
||||
id int,
|
||||
code string,
|
||||
actorId int,
|
||||
isAdmin bool,
|
||||
) error {
|
||||
if _, err := s.getScenarioForChange(ctx, id, actorId, isAdmin); err != nil {
|
||||
return err
|
||||
}
|
||||
story, err := s.getStory(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range story.Questions {
|
||||
if story.Questions[i].Code == code {
|
||||
story.Questions = append(story.Questions[:i], story.Questions[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
return s.updateStory(ctx, id, story)
|
||||
}
|
||||
|
||||
func (s *ScenarioService) getStory(ctx context.Context, id int) (*storytelling.Story, error) {
|
||||
storyString, err := s.scenariosRepo.GetStoryByScenarioID(ctx, id)
|
||||
if err != nil {
|
||||
@@ -432,7 +501,6 @@ func (s *ScenarioService) UploadArchive(
|
||||
return id, nil
|
||||
}
|
||||
|
||||
|
||||
func (s *ScenarioService) updateStory(ctx context.Context, id int, story *storytelling.Story) error {
|
||||
for _, place := range story.Places {
|
||||
place.Image = strings.TrimPrefix(place.Image, s.domain)
|
||||
@@ -478,6 +546,23 @@ func normalizeStory(story *storytelling.Story) (string, error) {
|
||||
}
|
||||
story.Places = cleanPlaces
|
||||
|
||||
// Коды вопросов уникальны, пустые коды отбрасываются (как у точек).
|
||||
questionCodes := map[string]struct{}{}
|
||||
for _, question := range story.Questions {
|
||||
questionCodes[question.Code] = struct{}{}
|
||||
}
|
||||
if len(questionCodes) != len(story.Questions) {
|
||||
return "", errors.New("Такой код вопроса уже существует")
|
||||
}
|
||||
cleanQuestions := make([]*storytelling.Question, 0, len(story.Questions))
|
||||
for _, question := range story.Questions {
|
||||
if question.Code == "" {
|
||||
continue
|
||||
}
|
||||
cleanQuestions = append(cleanQuestions, question)
|
||||
}
|
||||
story.Questions = cleanQuestions
|
||||
|
||||
return convertStory(story)
|
||||
}
|
||||
|
||||
|
||||
@@ -686,3 +686,132 @@ func mustJSON(t *testing.T, v scenario_archive.ScenarioJSON) []byte {
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func TestScenarioQuestionsCRUD(t *testing.T) {
|
||||
repo := newFakeScenariosRepo()
|
||||
storage := newFakeStorage()
|
||||
seedScenario(t, repo, storage)
|
||||
svc := newTestService(repo, storage)
|
||||
|
||||
// Добавление вопроса автором.
|
||||
err := svc.AddScenarioQuestion(context.Background(), 1, &storytelling.Question{
|
||||
Code: "q1",
|
||||
Text: "Кто убийца?",
|
||||
Answer: "Дворецкий",
|
||||
}, 7, false)
|
||||
if err != nil {
|
||||
t.Fatalf("AddScenarioQuestion: %v", err)
|
||||
}
|
||||
err = svc.AddScenarioQuestion(context.Background(), 1, &storytelling.Question{
|
||||
Code: "q2",
|
||||
Text: "Орудие?",
|
||||
Answer: "Канделябр",
|
||||
}, 7, false)
|
||||
if err != nil {
|
||||
t.Fatalf("AddScenarioQuestion: %v", err)
|
||||
}
|
||||
|
||||
full, err := svc.GetFullScenarioByID(context.Background(), 1)
|
||||
if err != nil {
|
||||
t.Fatalf("GetFullScenarioByID: %v", err)
|
||||
}
|
||||
if len(full.Story.Questions) != 2 {
|
||||
t.Fatalf("len(Questions) = %d, want 2", len(full.Story.Questions))
|
||||
}
|
||||
// Вопросы добавляются в конец списка: q1 добавлен первым.
|
||||
if full.Story.Questions[0].Code != "q1" || full.Story.Questions[0].Answer != "Дворецкий" {
|
||||
t.Errorf("Questions[0] = %+v, want q1 с правильным ответом", full.Story.Questions[0])
|
||||
}
|
||||
if full.Story.Questions[1].Code != "q2" || full.Story.Questions[1].Answer != "Канделябр" {
|
||||
t.Errorf("Questions[1] = %+v, want q2 с правильным ответом", full.Story.Questions[1])
|
||||
}
|
||||
|
||||
// Обновление вопроса по коду.
|
||||
err = svc.UpdateScenarioQuestion(context.Background(), 1, "q1", &storytelling.Question{
|
||||
Code: "q1",
|
||||
Text: "Кто убийца на самом деле?",
|
||||
Answer: "Экономка",
|
||||
}, 7, false)
|
||||
if err != nil {
|
||||
t.Fatalf("UpdateScenarioQuestion: %v", err)
|
||||
}
|
||||
full, err = svc.GetFullScenarioByID(context.Background(), 1)
|
||||
if err != nil {
|
||||
t.Fatalf("GetFullScenarioByID: %v", err)
|
||||
}
|
||||
found := false
|
||||
for _, q := range full.Story.Questions {
|
||||
if q.Code == "q1" {
|
||||
found = true
|
||||
if q.Text != "Кто убийца на самом деле?" || q.Answer != "Экономка" {
|
||||
t.Errorf("q1 = %+v, want обновлённый вопрос", q)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Error("вопрос q1 не найден после обновления")
|
||||
}
|
||||
|
||||
// Удаление вопроса.
|
||||
err = svc.DeleteScenarioQuestion(context.Background(), 1, "q2", 7, false)
|
||||
if err != nil {
|
||||
t.Fatalf("DeleteScenarioQuestion: %v", err)
|
||||
}
|
||||
full, err = svc.GetFullScenarioByID(context.Background(), 1)
|
||||
if err != nil {
|
||||
t.Fatalf("GetFullScenarioByID: %v", err)
|
||||
}
|
||||
if len(full.Story.Questions) != 1 || full.Story.Questions[0].Code != "q1" {
|
||||
t.Errorf("Questions = %+v, want только q1", full.Story.Questions)
|
||||
}
|
||||
|
||||
// Чужой автор не может менять вопросы.
|
||||
err = svc.AddScenarioQuestion(context.Background(), 1, &storytelling.Question{Code: "q3"}, 99, false)
|
||||
if !errors.Is(err, ErrScenarioNotOwner) {
|
||||
t.Errorf("AddScenarioQuestion чужой автор: err = %v, want ErrScenarioNotOwner", err)
|
||||
}
|
||||
err = svc.DeleteScenarioQuestion(context.Background(), 1, "q1", 99, false)
|
||||
if !errors.Is(err, ErrScenarioNotOwner) {
|
||||
t.Errorf("DeleteScenarioQuestion чужой автор: err = %v, want ErrScenarioNotOwner", err)
|
||||
}
|
||||
|
||||
// Админ может менять вопросы чужого сценария.
|
||||
err = svc.AddScenarioQuestion(context.Background(), 1, &storytelling.Question{
|
||||
Code: "q3", Text: "Вопрос админа",
|
||||
}, 1, true)
|
||||
if err != nil {
|
||||
t.Fatalf("AddScenarioQuestion админ: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeStoryQuestionCodes(t *testing.T) {
|
||||
repo := newFakeScenariosRepo()
|
||||
storage := newFakeStorage()
|
||||
seedScenario(t, repo, storage)
|
||||
svc := newTestService(repo, storage)
|
||||
|
||||
// Дубликат кода вопроса — ошибка.
|
||||
err := svc.AddScenarioQuestion(context.Background(), 1, &storytelling.Question{Code: "q1"}, 7, false)
|
||||
if err != nil {
|
||||
t.Fatalf("AddScenarioQuestion q1: %v", err)
|
||||
}
|
||||
err = svc.AddScenarioQuestion(context.Background(), 1, &storytelling.Question{Code: "q1"}, 7, false)
|
||||
if err == nil || !strings.Contains(err.Error(), "Такой код вопроса уже существует") {
|
||||
t.Errorf("AddScenarioQuestion с дублем кода: err = %v, want ошибку о дубле", err)
|
||||
}
|
||||
|
||||
// Пустой код — вопрос отбрасывается при сохранении.
|
||||
err = svc.AddScenarioQuestion(context.Background(), 1, &storytelling.Question{Code: "", Text: "без кода"}, 7, false)
|
||||
if err != nil {
|
||||
t.Fatalf("AddScenarioQuestion с пустым кодом: %v", err)
|
||||
}
|
||||
full, err := svc.GetFullScenarioByID(context.Background(), 1)
|
||||
if err != nil {
|
||||
t.Fatalf("GetFullScenarioByID: %v", err)
|
||||
}
|
||||
for _, q := range full.Story.Questions {
|
||||
if q.Code == "" {
|
||||
t.Error("вопрос с пустым кодом сохранился")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user