Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions tests/core/test_morpheme.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ def test_nighit(self):
self.assertEqual(nighit("สํ", "นิษฐาน"), "สันนิษฐาน")
self.assertEqual(nighit("สํ", "ปทา"), "สัมปทา")
self.assertEqual(nighit("สํ", "โยค"), "สังโยค")
with self.assertRaises(NotImplementedError):
nighit("abc", "คีต") # w1 does not end with ํ and len > 2
with self.assertRaises(NotImplementedError):
nighit("สํ", "มาร") # consonant ม is not in any supported group

def test_is_native_thai(self):
self.assertFalse(is_native_thai(None), False)
Expand Down
17 changes: 17 additions & 0 deletions tests/core/test_security.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,23 @@ def test_safe_extract_zip_rejects_path_traversal(self):
_safe_extract_zip(zf, extract_dir)
self.assertIn("path traversal", str(context.exception).lower())

def test_safe_extract_zip_rejects_symlink_escape(self):
"""Test that safe zip extraction rejects zip symlinks pointing outside."""
with tempfile.TemporaryDirectory() as tmpdir:
zip_path = os.path.join(tmpdir, "symlink_attack.zip")
with zipfile.ZipFile(zip_path, "w") as zf:
info = zipfile.ZipInfo("evil_symlink")
# Set Unix symlink mode: S_IFLNK = 0o120000
info.external_attr = 0o120777 << 16
zf.writestr(info, "../../etc/passwd")

extract_dir = os.path.join(tmpdir, "extract")
os.makedirs(extract_dir)
with zipfile.ZipFile(zip_path, "r") as zf:
with self.assertRaises(ValueError) as context:
_safe_extract_zip(zf, extract_dir)
self.assertIn("symlink", str(context.exception).lower())

def test_safe_extract_tar_rejects_symlink_escape(self):
"""Test that safe tar extraction rejects symlinks pointing outside."""
with tempfile.TemporaryDirectory() as tmpdir:
Expand Down
6 changes: 6 additions & 0 deletions tests/core/test_tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ def test_pos_tag(self):
],
)

def test_pos_tag_error_handling(self):
with self.assertRaises(ValueError):
pos_tag(["ทดสอบ"], engine="invalid_engine", corpus="invalid_corpus")
with self.assertRaises(ValueError):
pos_tag(["ทดสอบ"], engine="unigram", corpus="invalid_corpus")

def test_NER_error_handling(self):
with self.assertRaises(ValueError):
NER(engine="xx_non_existing", corpus="thainer")
Expand Down
15 changes: 15 additions & 0 deletions tests/core/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import unittest
from collections import Counter
from datetime import date, datetime, time, timedelta, timezone
from unittest.mock import patch

from pythainlp.corpus import corpus_path, thai_words
from pythainlp.util import (
Expand Down Expand Up @@ -61,6 +62,7 @@
thai_consonant_to_spelling,
thai_digit_to_arabic_digit,
thai_keyboard_dist,
thai_lunar_date,
thai_strftime,
thai_strptime,
thai_to_eng,
Expand Down Expand Up @@ -1017,10 +1019,18 @@ def test_remove_repeat_consonants(self):
def test_morse_encode(self):
self.assertEqual(morse_encode("แมว", lang="th"), ".-.- -- .--")
self.assertEqual(morse_encode("cat", lang="en"), "-.-. .- -")
with self.assertRaisesRegex(
NotImplementedError, "This function doesn't support jp"
):
morse_encode("แมว", lang="jp")

def test_morse_decode(self):
self.assertEqual(morse_decode(".-.- -- .--", lang="th"), "แมว")
self.assertEqual(morse_decode("-.-. .- -", lang="en"), "CAT")
with self.assertRaisesRegex(
NotImplementedError, "This function doesn't support jp"
):
morse_decode(".-.- -- .--", lang="jp")

def test_to_lunar_date(self):
self.assertEqual(to_lunar_date(date(2024, 11, 15)), "ขึ้น 15 ค่ำ เดือน 12")
Expand All @@ -1030,6 +1040,11 @@ def test_to_lunar_date(self):
self.assertEqual(to_lunar_date(date(2020, 10, 31)), "ขึ้น 15 ค่ำ เดือน 12")
with self.assertRaises(NotImplementedError):
to_lunar_date(date(1885, 9, 7)) # back to the future
with patch.object(thai_lunar_date, "last_day_in_year", return_value=353):
with self.assertRaisesRegex(
ValueError, "Unexpected last_day value: 353"
):
to_lunar_date(date(1903, 1, 1))

def test_th_zodiac(self):
self.assertEqual(th_zodiac(2024), "มะโรง")
Expand Down
122 changes: 121 additions & 1 deletion tests/extra/testx_transliterate.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@
import torch

from pythainlp.corpus import remove
from pythainlp.transliterate import pronunciate, puan, romanize, transliterate
from pythainlp.transliterate import (
lookup,
pronunciate,
puan,
romanize,
thai2rom,
thaig2p,
transliterate,
)
from pythainlp.transliterate.ipa import trans_list, xsampa_list
from pythainlp.transliterate.thai2rom import ThaiTransliterator
from pythainlp.transliterate.thai2rom_onnx import ThaiTransliterator_ONNX
Expand Down Expand Up @@ -101,6 +109,59 @@ def test_thai2rom_prepare_sequence(self):
.tolist(),
)

def test_thai2rom_unsupported_attention_method_raises_value_error(self):
attn = thai2rom.Attn(method="unsupported", hidden_size=4)
hidden = torch.randn(1, 1, 4)
encoder_outputs = torch.randn(1, 2, 4)
mask = torch.ones(1, 2, dtype=torch.bool)

with self.assertRaisesRegex(ValueError, "Unsupported attention method"):
attn(hidden, encoder_outputs, mask)

def test_thai2rom_seq2seq_hidden_mismatch_raises_value_error(self):
encoder = thai2rom.Encoder(
vocabulary_size=16,
embedding_size=8,
hidden_size=8,
dropout=0.0,
)
decoder = thai2rom.AttentionDecoder(
vocabulary_size=16,
embedding_size=8,
hidden_size=10,
dropout=0.0,
)

with self.assertRaisesRegex(
ValueError, "Encoder and decoder hidden sizes must match"
):
thai2rom.Seq2Seq(encoder, decoder, 2, 3, 10)

def test_thai2rom_seq2seq_inference_teacher_forcing_raises_value_error(self):
encoder = thai2rom.Encoder(
vocabulary_size=16,
embedding_size=8,
hidden_size=8,
dropout=0.0,
)
decoder = thai2rom.AttentionDecoder(
vocabulary_size=16,
embedding_size=8,
hidden_size=8,
dropout=0.0,
)
network = thai2rom.Seq2Seq(encoder, decoder, 2, 3, 10)

with self.assertRaisesRegex(
ValueError, "teacher_forcing_ratio must be zero during inference"
):
network(
torch.tensor([[1, 2, 0]], dtype=torch.long),
torch.tensor([2], dtype=torch.int),
None,
teacher_forcing_ratio=0.5,
)

def test_thai2rom_onnx_prepare_sequence(self):
transliterater = ThaiTransliterator_ONNX()

Expand Down Expand Up @@ -134,6 +195,65 @@ def test_thai2rom_onnx_prepare_sequence(self):
.tolist(),
)

def test_thaig2p_unsupported_attention_method_raises_value_error(self):
attn = thaig2p.Attn(method="unsupported", hidden_size=4)
hidden = torch.randn(1, 1, 4)
encoder_outputs = torch.randn(1, 2, 4)
mask = torch.ones(1, 2, dtype=torch.bool)

with self.assertRaisesRegex(ValueError, "Unsupported attention method"):
attn(hidden, encoder_outputs, mask)

def test_thaig2p_seq2seq_hidden_mismatch_raises_value_error(self):
encoder = thaig2p.Encoder(
vocabulary_size=16,
embedding_size=8,
hidden_size=8,
dropout=0.0,
)
decoder = thaig2p.AttentionDecoder(
vocabulary_size=16,
embedding_size=8,
hidden_size=10,
dropout=0.0,
)

with self.assertRaisesRegex(
ValueError, "Encoder and decoder hidden sizes must match"
):
thaig2p.Seq2Seq(encoder, decoder, 2, 3, 10)

def test_thaig2p_seq2seq_inference_teacher_forcing_raises_value_error(self):
encoder = thaig2p.Encoder(
vocabulary_size=16,
embedding_size=8,
hidden_size=8,
dropout=0.0,
)
decoder = thaig2p.AttentionDecoder(
vocabulary_size=16,
embedding_size=8,
hidden_size=8,
dropout=0.0,
)
network = thaig2p.Seq2Seq(encoder, decoder, 2, 3, 10)

with self.assertRaisesRegex(
ValueError, "teacher_forcing_ratio must be zero during inference"
):
network(
torch.tensor([[1, 2, 0]], dtype=torch.long),
[2],
None,
teacher_forcing_ratio=0.5,
)

def test_lookup_non_callable_fallback_raises_type_error(self):
with self.assertRaisesRegex(
TypeError, "`fallback_engine` is not callable"
):
lookup.romanize("___", fallback_func="not-callable") # type: ignore[arg-type]

def test_transliterate(self):
self.assertEqual(transliterate("แมว", "pyicu"), "mæw")
self.assertEqual(transliterate("คน", engine="ipa"), "kʰon")
Expand Down
Loading