Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,9 @@ dmypy.json

# Pyre type checker
.pyre/

# Bluezip
/bluezip/bluezip.db

# Testing
/repack
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "bluezip"]
path = bluezip
url = https://github.com/FlashpointProject/bluezip.git
11 changes: 11 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM python:3.10

WORKDIR /usr/local/bin

COPY ./ ./

RUN pip install -r requirements.txt
RUN pip install -r ./bluezip/requirements.txt
RUN python -m pytest

CMD ["python", "-m", "uvicorn", "validator-server:app", "--host", "0.0.0.0", "--port", "8000"]
1 change: 1 addition & 0 deletions bluezip
Submodule bluezip added at e2b1e4
423 changes: 398 additions & 25 deletions curation_validator.py

Large diffs are not rendered by default.

123 changes: 104 additions & 19 deletions curation_validator_test.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import os
import asyncio
import pytest
import unittest
from unittest.mock import patch
from repack import repack
from datetime import datetime

from curation_validator import validate_curation, CurationType
from curation_validator import validate_curation, CurationType, is_date_more_than_three_years_ago

pytest_plugins = ('pytest_asyncio',)

os.environ["REPACK_DIR"] = os.path.dirname(os.path.realpath(__file__)) + '/repack/'


def mock_get_tag_list() -> list[str]:
Expand Down Expand Up @@ -35,21 +44,24 @@ def test_valid_yaml_meta(self):

def test_invalid_yaml_meta_extreme(self):
for extension in ["7z", "zip"]:
errors, warnings, is_extreme, _, _, _ = validate_curation(f"test_curations/test_curation_invalid_extreme.{extension}")
errors, warnings, is_extreme, _, _, _ = validate_curation(
f"test_curations/test_curation_invalid_extreme.{extension}")
self.assertCountEqual(errors, ["Curation is extreme but lacks extreme tags."])
self.assertCountEqual(warnings, [])
self.assertTrue(is_extreme)

def test_valid_yaml_meta_extreme(self):
for extension in ["7z", "zip"]:
errors, warnings, is_extreme, _, _, _ = validate_curation(f"test_curations/test_curation_valid_extreme.{extension}")
errors, warnings, is_extreme, _, _, _ = validate_curation(
f"test_curations/test_curation_valid_extreme.{extension}")
self.assertCountEqual(errors, [])
self.assertCountEqual(warnings, [])
self.assertTrue(is_extreme)

def test_valid_legacy(self):
for extension in ["7z", "zip"]:
errors, warnings, is_extreme, _, _, _ = validate_curation(f"test_curations/test_curation_valid_legacy.{extension}")
errors, warnings, is_extreme, _, _, _ = validate_curation(
f"test_curations/test_curation_valid_legacy.{extension}")
self.assertCountEqual(errors, [])
self.assertCountEqual(warnings, [])
self.assertFalse(is_extreme)
Expand All @@ -71,7 +83,8 @@ def test_curation_invalid_archive(self):

def test_curation_empty_meta(self):
for extension in ["7z", "zip"]:
errors, warnings, is_extreme, _, _, _ = validate_curation(f"test_curations/test_curation_empty_meta.{extension}")
errors, warnings, is_extreme, _, _, _ = validate_curation(
f"test_curations/test_curation_empty_meta.{extension}")
self.assertCountEqual(errors, ["The meta file seems to be empty."])
self.assertCountEqual(warnings, [])

Expand Down Expand Up @@ -117,7 +130,8 @@ def test_empty_content(self):

def test_missing_content(self):
for extension in ["7z", "zip"]:
errors, warnings, _, _, _, _ = validate_curation(f"test_curations/test_curation_missing_content.{extension}")
errors, warnings, _, _, _, _ = validate_curation(
f"test_curations/test_curation_missing_content.{extension}")
self.assertCountEqual(errors, ["Content folder not found."])
self.assertCountEqual(warnings, [])

Expand All @@ -137,7 +151,8 @@ def test_missing_meta(self):

def test_missing_root_folder(self):
for extension in ["7z", "zip"]:
errors, warnings, _, _, _, _ = validate_curation(f"test_curations/test_curation_missing_root_folder.{extension}")
errors, warnings, _, _, _, _ = validate_curation(
f"test_curations/test_curation_missing_root_folder.{extension}")
self.assertCountEqual(errors, [
"Logo, screenshot, content folder and meta not found. Is your curation structured properly?"])
self.assertCountEqual(warnings, [])
Expand Down Expand Up @@ -177,32 +192,37 @@ def test_missing_application_path_warning(self):

def test_missing_launch_command(self):
for extension in ["7z", "zip"]:
errors, warnings, _, _, _, _ = validate_curation(f"test_curations/test_curation_missing_launch_command.{extension}")
errors, warnings, _, _, _, _ = validate_curation(
f"test_curations/test_curation_missing_launch_command.{extension}")
self.assertCountEqual(errors, ["The `Launch Command` property in the meta file is mandatory."])
self.assertCountEqual(warnings, [])

def test_missing_languages(self):
for extension in ["7z", "zip"]:
errors, warnings, _, _, _, _ = validate_curation(f"test_curations/test_curation_missing_languages.{extension}")
errors, warnings, _, _, _, _ = validate_curation(
f"test_curations/test_curation_missing_languages.{extension}")
self.assertCountEqual(errors, ["The `Languages` property in the meta file is mandatory."])
self.assertCountEqual(warnings, [])

def test_comma_in_languages(self):
for extension in ["7z", "zip"]:
errors, warnings, _, _, _, _ = validate_curation(f"test_curations/test_curation_comma_in_languages.{extension}")
errors, warnings, _, _, _, _ = validate_curation(
f"test_curations/test_curation_comma_in_languages.{extension}")
self.assertCountEqual(errors, ["Languages should be separated with semicolons, not commas."])
self.assertCountEqual(warnings, [])

def test_common_bad_language(self):
for extension in ["7z", "zip"]:
errors, warnings, _, _, _, _ = validate_curation(f"test_curations/test_curation_common_bad_language.{extension}")
errors, warnings, _, _, _, _ = validate_curation(
f"test_curations/test_curation_common_bad_language.{extension}")
self.assertCountEqual(errors, ["The correct ISO 639-1 language code for Japanese is `ja`, not `jp`."])
self.assertCountEqual(warnings, [])

def test_language_name(self):
for extension in ["7z", "zip"]:
errors, warnings, _, _, _, _ = validate_curation(f"test_curations/test_curation_language_name.{extension}")
self.assertCountEqual(errors, ["Languages must be in ISO 639-1 format, so please use `ja` instead of `Japanese`"])
self.assertCountEqual(errors,
["Languages must be in ISO 639-1 format, so please use `ja` instead of `Japanese`"])
self.assertCountEqual(warnings, [])

def test_missing_source(self):
Expand Down Expand Up @@ -230,7 +250,8 @@ def test_rar(self):

def test_trailing_language_semicolon(self):
for extension in ["7z", "zip"]:
errors, warnings, _, _, _, _ = validate_curation(f"test_curations/test_curation_languages_semicolon.{extension}")
errors, warnings, _, _, _, _ = validate_curation(
f"test_curations/test_curation_languages_semicolon.{extension}")
self.assertCountEqual(errors, [])
self.assertCountEqual(warnings, [])

Expand All @@ -242,29 +263,93 @@ def test_valid_date(self):

def test_localflash_too_many_files(self):
for extension in ["7z", "zip"]:
errors, warnings, _, _, _, _ = validate_curation(f"test_curations/test_curation_localflash_too_many_files.{extension}")
self.assertCountEqual(errors, ["Content must be in additional folder in localflash rather than in localflash directly."])
errors, warnings, _, _, _, _ = validate_curation(
f"test_curations/test_curation_localflash_too_many_files.{extension}")
self.assertCountEqual(errors, [
"Content must be in additional folder in localflash rather than in localflash directly."])
self.assertCountEqual(warnings, [])

def test_localflash_no_folder(self):
for extension in ["7z", "zip"]:
errors, warnings, _, _, _, _ = validate_curation(f"test_curations/test_curation_localflash_no_folder.{extension}")
self.assertCountEqual(errors, ["Content must be in additional folder in localflash rather than in localflash directly."])
errors, warnings, _, _, _, _ = validate_curation(
f"test_curations/test_curation_localflash_no_folder.{extension}")
self.assertCountEqual(errors, [
"Content must be in additional folder in localflash rather than in localflash directly."])
self.assertCountEqual(warnings, [])

def test_localflash_bad_name(self):
for extension in ["7z", "zip"]:
errors, warnings, _, _, _, _ = validate_curation(f"test_curations/test_curation_localflash_bad_name.{extension}")
errors, warnings, _, _, _, _ = validate_curation(
f"test_curations/test_curation_localflash_bad_name.{extension}")
self.assertCountEqual(errors, ["Extremely common localflash containing folder name, please change."])
self.assertCountEqual(warnings, [])

def test_no_library(self):
for extension in ["7z", "zip"]:
errors, warnings, _, curation_type, _, _ = validate_curation(f"test_curations/test_curation_none_library.{extension}")
errors, warnings, _, curation_type, _, _ = validate_curation(
f"test_curations/test_curation_none_library.{extension}")
self.assertCountEqual(errors, [])
self.assertCountEqual(warnings, [])
self.assertEqual(curation_type, CurationType.FLASH_GAME)

def test_convert_platform_field(self):
for extension in ["7z", "zip"]:
errors, warnings, _, _, meta, _ = validate_curation(f"test_curations/test_curation_valid.{extension}")
self.assertCountEqual(errors, [])
self.assertCountEqual(warnings, [])
self.assertEqual(meta["Platforms"], "Flash")

def test_addapps(self):
for extension in ["7z", "zip"]:
errors, warnings, _, _, meta, _ = validate_curation(
f"test_curations/test_curation_valid_addapps.{extension}")
self.assertCountEqual(errors, [])
self.assertEqual(meta["Extras"], "test")
self.assertEqual(meta["Message"], "test")
self.assertEqual(len(meta["Additional Applications"]), 1)
self.assertEqual(meta["Additional Applications"][0]["Heading"], "Test")
self.assertEqual(meta["Additional Applications"][0]["Application Path"], "test")
self.assertEqual(meta["Additional Applications"][0]["Launch Command"], "test")

def test_primary_platform(self):
for extension in ["7z", "zip"]:
# From empty
errors, warnings, _, _, meta, _ = validate_curation(f"test_curations/test_curation_valid.{extension}")
self.assertCountEqual(errors, [])
self.assertEqual(meta["Primary Platform"], "Flash")
# From stated
errors, warnings, _, _, meta, _ = validate_curation(
f"test_curations/test_curation_primary_platform.{extension}")
self.assertCountEqual(errors, [])
self.assertEqual(meta["Primary Platform"], "HTML5")

def test_ruffle_support(self):
for extension in ["7z", "zip"]:
errors, warnings, _, _, meta, _ = validate_curation(
f"test_curations/test_curation_invalid_ruffle.{extension}")
self.assertNotEqual(len(errors), 0)


@pytest.mark.asyncio
async def test_bluezip():
for extension in ["7z", "zip"]:
errors, output = await repack(f"test_curations/test_curation_valid.{extension}")
assert len(errors) == 0
assert os.path.exists(output)


def test_is_date_more_than_three_years_ago():
cases = [
{"now": datetime(2003, 1, 1), "year": 2000, "month": 1, "day": 1, "return": True},
{"now": datetime(2003, 1, 1), "year": 2000, "month": 1, "day": 2, "return": False},
{"now": datetime(2003, 1, 1), "year": 2002, "month": 2, "day": 20, "return": False},
{"now": datetime(2003, 1, 1), "year": 2002, "month": None, "day": None, "return": False},
{"now": datetime(2003, 1, 1), "year": 2002, "month": 1, "day": None, "return": False},
]

for case in cases:
assert case["return"] == is_date_more_than_three_years_ago(case["now"], case["year"], case["month"], case["day"]), case


if __name__ == '__main__':
unittest.main()
9 changes: 9 additions & 0 deletions data/blacklisted_tags.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"tags": [
"Cub",
"Lolicon",
"Shotacon",
"Toddlercon",
"Teenager"
]
}
63 changes: 52 additions & 11 deletions data/category_tags.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,56 @@
{
"tags": [
"Action",
"Adventure",
"Arcade",
"Creative",
"Educational",
"Puzzle",
"Simulation",
"Sports",
"Strategy",
"Other",
"Game Jam"
{
"name": "Action",
"description": "These games are active and action-packed, whether they involve running, jumping, shooting, or something else."
},
{
"name": "Adventure",
"description": "In Adventure games, the player experiences an interactive story driven by exploration and/or puzzle solving."
},
{
"name": "Arcade",
"description": "Arcade games have easily graspable gameplay and a focus on getting a high score."
},
{
"name": "Card",
"description": "Games where the gameplay revolves around using cards. Some are accompanied by the 'Gambling' tag."
},
{
"name": "Creative",
"description": "Games facilitating user made content, from customizing characters to coloring to making your own picture."
},
{
"name": "Educational",
"description": "Educational games aim to teach something as you play."
},
{
"name": "Mathematical",
"description": "Games which are generally themed around fields of recreational mathematics."
},
{
"name": "Puzzle",
"description": "Puzzle games involve brainteasers of all types, from fast-paced matching games to hidden object puzzles."
},
{
"name": "Simulation",
"description": "This type of a game attempts to simulate something from real life, like a card game or just walking around."
},
{
"name": "Sports",
"description": "Sports games attempt to replicate the gameplay of physical sports. This also encompasses fictional sports such as Quidditch. A game does not have to perfectly replicate a sport, so long as it is inspired by sports."
},
{
"name": "Strategy",
"description": "Strategy games come in many forms, but usually involve controlling units in an efficient way to defeat some kind of enemy."
},
{
"name": "Other",
"description": "If a game doesn't seem to fit anywhere else, it will probably fit in one of these tags."
},
{
"name": "Game Jam",
"description": "Games created from scratch during a very limited amount of time, usually between 24 to 72 hours, following a provided theme."
}
]
}
5 changes: 0 additions & 5 deletions data/extreme_tags.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"tags": [
"Cub",
"Inflation",
"Bestiality",
"Cannibalism",
Expand All @@ -9,11 +8,8 @@
"Fisting",
"Flatulence",
"Frottage",
"Lolicon",
"Necrophilia",
"Scat",
"Shotacon",
"Toddlercon",
"Vomit",
"Vore",
"Homophobia",
Expand Down Expand Up @@ -46,7 +42,6 @@
"Sexual Violence",
"Sex Toys",
"Spanking",
"Teenager",
"Tentacles",
"Touching",
"Tribadism",
Expand Down
3 changes: 3 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[pytest]
addopts = --ignore=bluezip
asyncio_mode=auto
Loading