vault backup: 2025-12-26 02:09:22
All checks were successful
Deploy Quartz site to GitHub Pages / build (push) Successful in 2m29s
All checks were successful
Deploy Quartz site to GitHub Pages / build (push) Successful in 2m29s
This commit is contained in:
261
stroma/quiz/utils/tests/test_importer.py
Normal file
261
stroma/quiz/utils/tests/test_importer.py
Normal file
@@ -0,0 +1,261 @@
|
||||
import pytest
|
||||
import time
|
||||
from pathlib import Path
|
||||
from quiz.utils.importer import parse_markdown_question, import_question_file, ImportStats
|
||||
from quiz.models import Question, Option
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.import_tests
|
||||
class TestMarkdownParsing:
|
||||
"""Test parsing of various Obsidian markdown question formats"""
|
||||
|
||||
def test_parse_single_choice_question(self):
|
||||
"""Test parsing standard single choice question (SCQ)"""
|
||||
content = """---
|
||||
tags: [ah2, provfråga, frågetyp/scq, anatomi]
|
||||
date: 2022-01-15
|
||||
---
|
||||
What is the correct answer?
|
||||
|
||||
**Välj ett alternativ:**
|
||||
- A: Wrong answer
|
||||
- B: Correct answer
|
||||
- C: Another wrong
|
||||
|
||||
```spoiler-block:
|
||||
B
|
||||
```
|
||||
"""
|
||||
is_question, data = parse_markdown_question(Path("test.md"), content)
|
||||
|
||||
assert is_question is True
|
||||
assert data['text'] == 'What is the correct answer?'
|
||||
assert data['correct_answer'] == 'B'
|
||||
assert data['has_answer'] is True
|
||||
assert data['question_type'] == 'scq'
|
||||
assert len(data['options']) == 3
|
||||
assert data['options'][0] == ('A', 'Wrong answer')
|
||||
assert data['options'][1] == ('B', 'Correct answer')
|
||||
|
||||
def test_parse_multiple_choice_question(self):
|
||||
"""Test parsing multiple choice question (MCQ) with 'och' separator"""
|
||||
content = """---
|
||||
tags: [ah2, provfråga, frågetyp/mcq, cerebrum]
|
||||
date: 2022-01-15
|
||||
---
|
||||
Vilka av följande räknas till storhjärnans basala kärnor?
|
||||
|
||||
**Välj två alternativ**
|
||||
- A: Putamen
|
||||
- B: Nucleus Ruber
|
||||
- C: Substantia nigra
|
||||
- D: Nucleus caudatus
|
||||
|
||||
```spoiler-block:
|
||||
A och D
|
||||
```
|
||||
"""
|
||||
is_question, data = parse_markdown_question(Path("test.md"), content)
|
||||
|
||||
assert is_question is True
|
||||
assert 'Vilka av följande' in data['text']
|
||||
assert data['correct_answer'] == 'A,D' # Normalized to comma-separated
|
||||
assert data['has_answer'] is True
|
||||
assert data['question_type'] == 'mcq'
|
||||
assert len(data['options']) == 4
|
||||
|
||||
def test_parse_multiple_choice_comma_separated(self):
|
||||
"""Test MCQ with comma-separated answer"""
|
||||
content = """---
|
||||
tags: [frågetyp/mcq]
|
||||
---
|
||||
Select two options:
|
||||
|
||||
- A: Option A
|
||||
- B: Option B
|
||||
- C: Option C
|
||||
- D: Option D
|
||||
|
||||
```spoiler-block:
|
||||
B, C
|
||||
```
|
||||
"""
|
||||
is_question, data = parse_markdown_question(Path("test.md"), content)
|
||||
|
||||
assert data['correct_answer'] == 'B,C'
|
||||
assert data['has_answer'] is True
|
||||
|
||||
def test_parse_matching_question(self):
|
||||
"""Test parsing matching question (DND/Matching)"""
|
||||
content = """---
|
||||
tags: [ah2, provfråga, frågetyp/matching, anatomi, öra]
|
||||
date: 2023-05-31
|
||||
---
|
||||
**Matcha rätt funktion med rätt lob:**
|
||||
(1p för alla rätt, inga delpoäng)
|
||||
- Smak
|
||||
- Syn
|
||||
- Somatosensorik
|
||||
- Motorik
|
||||
- Hörsel
|
||||
|
||||
**Alternativ:**
|
||||
|
||||
- Lobus frontalis
|
||||
- Lobus Insularis
|
||||
- Lobus temporalis
|
||||
- Lobus parietalis
|
||||
- Lobus occipitalis
|
||||
|
||||
```spoiler-block:
|
||||
Smak: Lobus Insularis
|
||||
Syn: Lobus occipitalis
|
||||
Somatosensorik: Lobus parietalis
|
||||
Motorik: Lobus frontalis
|
||||
Hörsel: Lobus temporalis
|
||||
```
|
||||
"""
|
||||
is_matching, data = parse_markdown_question(Path("test.md"), content)
|
||||
|
||||
assert is_matching is True
|
||||
assert data['question_type'] == 'matching'
|
||||
assert data['has_answer'] is True
|
||||
assert len(data['left_items']) == 5
|
||||
assert len(data['top_items']) == 5
|
||||
assert len(data['correct_pairs']) == 5
|
||||
|
||||
def test_parse_textalternativ_question(self):
|
||||
"""Test text alternative question type"""
|
||||
content = """---
|
||||
tags: [frågetyp/textalternativ, öga, anatomi]
|
||||
---
|
||||
Svara på följande frågor:
|
||||
|
||||
a) Bokstaven B sitter i en lob, vilken?
|
||||
- Lobus temporalis
|
||||
- Lobus frontalis
|
||||
- Lobus parietalis
|
||||
|
||||
b) Vilket funktionellt centra återfinns där?
|
||||
- Syncentrum
|
||||
- Motorcentrum
|
||||
- Somatosensoriskt centrum
|
||||
|
||||
```spoiler-block:
|
||||
a) Lobus parietalis
|
||||
b) Somatosensoriskt centrum
|
||||
```
|
||||
"""
|
||||
is_question, data = parse_markdown_question(Path("test.md"), content)
|
||||
|
||||
assert is_question is True
|
||||
assert data['question_type'] == 'textalternativ'
|
||||
assert data['has_answer'] is True
|
||||
assert 'Lobus parietalis' in data['correct_answer']
|
||||
assert 'Somatosensoriskt centrum' in data['correct_answer']
|
||||
|
||||
def test_parse_textfalt_question(self):
|
||||
"""Test text field (fill-in) question type"""
|
||||
content = """---
|
||||
tags: [frågetyp/textfält, öga]
|
||||
---
|
||||
**Fyll i rätt siffra!**
|
||||
|
||||
a) Vilken siffra pekar på gula fläcken?
|
||||
b) Vilken siffra pekar på choroidea?
|
||||
|
||||
```spoiler-block:
|
||||
a) 7
|
||||
b) 6
|
||||
```
|
||||
"""
|
||||
is_question, data = parse_markdown_question(Path("test.md"), content)
|
||||
|
||||
assert is_question is True
|
||||
assert data['question_type'] == 'textfält'
|
||||
assert data['has_answer'] is True
|
||||
assert '7' in data['correct_answer']
|
||||
assert '6' in data['correct_answer']
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.import_tests
|
||||
class TestQuestionImport:
|
||||
"""Test actual import of questions to database"""
|
||||
|
||||
def test_import_single_question(self, tmp_path):
|
||||
"""Test importing a single question file"""
|
||||
question_file = tmp_path / "question1.md"
|
||||
question_file.write_text("""---
|
||||
tags: [frågetyp/scq]
|
||||
---
|
||||
Test question?
|
||||
|
||||
- A: Correct
|
||||
- B: Wrong
|
||||
|
||||
```spoiler-block:
|
||||
A
|
||||
```
|
||||
""")
|
||||
|
||||
stats = ImportStats()
|
||||
result = import_question_file(question_file, tmp_path, stats, force=True)
|
||||
|
||||
assert result in ['imported', 'updated']
|
||||
assert stats.questions_with_answers == 1
|
||||
|
||||
# Verify in database
|
||||
question = Question.objects.get(text='Test question?')
|
||||
assert question.correct_answer == 'A'
|
||||
assert question.options.count() == 2
|
||||
|
||||
def test_mtime_tracking(self, tmp_path):
|
||||
"""Test that file modification time is tracked"""
|
||||
question_file = tmp_path / "question4.md"
|
||||
question_file.write_text("""---
|
||||
tags: [frågetyp/scq]
|
||||
---
|
||||
What is the correct answer?
|
||||
```spoiler-block:
|
||||
A
|
||||
```
|
||||
""")
|
||||
|
||||
stats = ImportStats()
|
||||
import_question_file(question_file, tmp_path, stats, force=True)
|
||||
|
||||
question = Question.objects.get(text='What is the correct answer?')
|
||||
assert question.file_mtime == question_file.stat().st_mtime
|
||||
|
||||
def test_update_existing_question(self, tmp_path):
|
||||
"""Test updating an existing question"""
|
||||
question_file = tmp_path / "question5.md"
|
||||
|
||||
# Initial import
|
||||
question_file.write_text("""---
|
||||
tags: [frågetyp/scq]
|
||||
---
|
||||
Question to update?
|
||||
```spoiler-block:
|
||||
A
|
||||
```
|
||||
""")
|
||||
|
||||
import_question_file(question_file, tmp_path, ImportStats(), force=True)
|
||||
|
||||
# Update the file
|
||||
time.sleep(0.1)
|
||||
question_file.write_text("""---
|
||||
tags: [frågetyp/scq]
|
||||
---
|
||||
Question to update?
|
||||
```spoiler-block:
|
||||
B
|
||||
```
|
||||
""")
|
||||
|
||||
stats = ImportStats()
|
||||
result = import_question_file(question_file, tmp_path, stats, force=False)
|
||||
|
||||
assert result == 'updated'
|
||||
assert Question.objects.get(text='Question to update?').correct_answer == 'B'
|
||||
Reference in New Issue
Block a user