diff --git a/schema/Dockerfile.test b/schema/Dockerfile.test index 9d30a6ba7..50edfffd9 100644 --- a/schema/Dockerfile.test +++ b/schema/Dockerfile.test @@ -19,6 +19,6 @@ USER owasp COPY poetry.lock pyproject.toml ./ RUN poetry install --no-root -COPY project.json project.json +COPY *.json ./ COPY tests tests COPY utils utils diff --git a/schema/chapter.json b/schema/chapter.json new file mode 100644 index 000000000..18c4446c3 --- /dev/null +++ b/schema/chapter.json @@ -0,0 +1,140 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://raw.githubusercontent.com/OWASP/Nest/main/schema/chapter.json", + "$defs": { + "leader": { + "type": "object", + "title": "Leader", + "description": "A chapter leader.", + "required": ["github"], + "properties": { + "email": { + "description": "The leader's email address.", + "format": "email", + "type": ["string", "null"] + }, + "github": { + "type": "string", + "description": "The GitHub username.", + "pattern": "^[a-zA-Z0-9-]{1,39}$" + }, + "name": { + "type": ["string", "null"], + "description": "Leader's name." + }, + "slack": { + "type": ["string", "null"], + "description": "The Slack username.", + "pattern": "^[a-zA-Z0-9._-]{1,21}$" + } + }, + "additionalProperties": false + }, + "sponsor": { + "type": "object", + "title": "Sponsor", + "description": "A chapter sponsor.", + "required": [ + "name", + "url" + ], + "properties": { + "description": { + "type": "string", + "description": "A brief description of the sponsor." + }, + "logo": { + "type": "string", + "description": "The URL of the sponsor's logo.", + "format": "uri" + }, + "name": { + "type": "string", + "description": "The name of the sponsor or organization." + }, + "url": { + "type": "string", + "description": "The URL of the sponsor.", + "format": "uri" + } + }, + "additionalProperties": false + } + }, + + "additionalProperties": false, + "description": "OWASP chapter schema.", + "properties": { + "blog": { + "description": "Chapter's blog.", + "format": "uri", + "type": ["string"] + }, + "country": { + "description": "Country.", + "type": "string" + }, + "events": { + "description": "Event URLs related to the chapter.", + "type": "array", + "items": { + "format": "uri", + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + }, + "leaders": { + "description": "Leaders of the chapter.", + "type": "array", + "items": { + "$ref": "#/$defs/leader" + }, + "minItems": 2, + "uniqueItems": true + }, + "meetup-group": { + "description": "Meetup group.", + "type": "string" + }, + "name": { + "description": "The unique name of the chapter.", + "minLength": 10, + "type": "string" + }, + "region": { + "description": "Region.", + "type": "string" + }, + "sponsors": { + "description": "Sponsors of the chapter.", + "type": "array", + "items": { + "$ref": "#/$defs/sponsor" + }, + "minItems": 1 + }, + "tags": { + "description": "Tags for the chapter", + "type": "array", + "items": { + "type": "string" + }, + "minItems": 3, + "uniqueItems": true + }, + "website": { + "description": "The official website of the chapter.", + "type": "string", + "format": "url" + } + }, + "required": [ + "country", + "leaders", + "name", + "tags" + ], + "title": "OWASP Chapter", + "type": "object" +} diff --git a/schema/tests/chapter_test.py b/schema/tests/chapter_test.py new file mode 100644 index 000000000..7395aa839 --- /dev/null +++ b/schema/tests/chapter_test.py @@ -0,0 +1,57 @@ +from pathlib import Path + +import pytest +import yaml +from utils.validators import validate_data + +from tests.conftest import tests_data_dir + + +def test_positive(chapter_schema): + for file_path in Path(tests_data_dir / "chapter/positive").rglob("*.yaml"): + assert ( + validate_data( + chapter_schema, + yaml.safe_load( + file_path.read_text(), + ), + ) + is None + ) + + +@pytest.mark.parametrize( + ("file_path", "error_message"), + [ + ("blog-none.yaml", "None is not of type 'string'"), + ("events-empty.yaml", "[] should be non-empty"), + ( + "events-non-unique-urls.yaml", + "['https://example.com/event1', 'https://example.com/event1'] has non-unique elements", + ), + ( + "leader-email-empty.yaml", + "[{'email': '', 'github': 'leader-1-github', 'name': 'Leader 1 Name'}] is too short", + ), + ( + "leader-email-missing.yaml", + "[{'email': None, 'github': 'leader-1-github', 'name': 'Leader 1 Name'}] is too short", + ), + ("name-empty.yaml", "'' is too short"), + ("name-none.yaml", "None is not of type 'string'"), + ("sponsors-empty-list.yaml", "[] should be non-empty"), + ("sponsors-name-missing.yaml", "'name' is a required property"), + ("sponsors-url-missing.yaml", "'url' is a required property"), + ("website-none.yaml", "None is not of type 'string'"), + ], +) +def test_negative(chapter_schema, file_path, error_message): + assert ( + validate_data( + chapter_schema, + yaml.safe_load( + Path(tests_data_dir / "chapter/negative" / file_path).read_text(), + ), + ) + == error_message + ) diff --git a/schema/tests/conftest.py b/schema/tests/conftest.py index 062478b14..39df8b773 100644 --- a/schema/tests/conftest.py +++ b/schema/tests/conftest.py @@ -8,6 +8,12 @@ schema_dir = tests_dir.parent +@pytest.fixture +def chapter_schema(): + with Path(schema_dir / "chapter.json").open() as file: + yield json.load(file) + + @pytest.fixture def project_schema(): with Path(schema_dir / "project.json").open() as file: diff --git a/schema/tests/data/chapter/negative/blog-none.yaml b/schema/tests/data/chapter/negative/blog-none.yaml new file mode 100644 index 000000000..7eafe3041 --- /dev/null +++ b/schema/tests/data/chapter/negative/blog-none.yaml @@ -0,0 +1,12 @@ +blog: +country: India +leaders: + - github: leader-1-github + name: Leader 1 Name + - github: leader-2-github + name: Leader 2 Name +name: OWASP Chapter +tags: + - example-tag-1 + - example-tag-2 + - example-tag-3 diff --git a/schema/tests/data/chapter/negative/events-empty.yaml b/schema/tests/data/chapter/negative/events-empty.yaml new file mode 100644 index 000000000..4e2ac788f --- /dev/null +++ b/schema/tests/data/chapter/negative/events-empty.yaml @@ -0,0 +1,13 @@ +country: India +leaders: + - github: leader-1-github + name: Leader 1 Name + - github: leader-2-github + name: Leader 2 Name + slack: leader-2-slack +name: OWASP Chapter +tags: + - example-tag-1 + - example-tag-2 + - example-tag-3 +events: [] diff --git a/schema/tests/data/chapter/negative/events-non-unique-urls.yaml b/schema/tests/data/chapter/negative/events-non-unique-urls.yaml new file mode 100644 index 000000000..b8218cc23 --- /dev/null +++ b/schema/tests/data/chapter/negative/events-non-unique-urls.yaml @@ -0,0 +1,15 @@ +country: India +leaders: + - github: leader-1-github + name: Leader 1 Name + - github: leader-2-github + name: Leader 2 Name + slack: leader-2-slack +name: OWASP Chapter +tags: + - example-tag-1 + - example-tag-2 + - example-tag-3 +events: + - https://example.com/event1 + - https://example.com/event1 diff --git a/schema/tests/data/chapter/negative/leader-email-empty.yaml b/schema/tests/data/chapter/negative/leader-email-empty.yaml new file mode 100644 index 000000000..10b2f6fea --- /dev/null +++ b/schema/tests/data/chapter/negative/leader-email-empty.yaml @@ -0,0 +1,10 @@ +country: India +leaders: + - email: '' + github: leader-1-github + name: Leader 1 Name +name: OWASP Chapter +tags: + - example-tag-1 + - example-tag-2 + - example-tag-3 diff --git a/schema/tests/data/chapter/negative/leader-email-missing.yaml b/schema/tests/data/chapter/negative/leader-email-missing.yaml new file mode 100644 index 000000000..a982b4ae8 --- /dev/null +++ b/schema/tests/data/chapter/negative/leader-email-missing.yaml @@ -0,0 +1,10 @@ +country: India +leaders: + - email: + github: leader-1-github + name: Leader 1 Name +name: OWASP Chapter +tags: + - example-tag-1 + - example-tag-2 + - example-tag-3 diff --git a/schema/tests/data/chapter/negative/name-empty.yaml b/schema/tests/data/chapter/negative/name-empty.yaml new file mode 100644 index 000000000..0e90112fa --- /dev/null +++ b/schema/tests/data/chapter/negative/name-empty.yaml @@ -0,0 +1,11 @@ +country: India +leaders: + - github: leader-name-1 + name: Leader Name 1 + - github: leader-name-2 + name: Leader Name 2 +name: '' +tags: + - example-tag-1 + - example-tag-2 + - example-tag-3 diff --git a/schema/tests/data/chapter/negative/name-none.yaml b/schema/tests/data/chapter/negative/name-none.yaml new file mode 100644 index 000000000..242c08e3b --- /dev/null +++ b/schema/tests/data/chapter/negative/name-none.yaml @@ -0,0 +1,11 @@ +country: India +leaders: + - github: leader-name-1 + name: Leader Name 1 + - github: leader-name-2 + name: Leader Name 2 +name: +tags: + - example-tag-1 + - example-tag-2 + - example-tag-3 diff --git a/schema/tests/data/chapter/negative/sponsors-empty-list.yaml b/schema/tests/data/chapter/negative/sponsors-empty-list.yaml new file mode 100644 index 000000000..1aa848e88 --- /dev/null +++ b/schema/tests/data/chapter/negative/sponsors-empty-list.yaml @@ -0,0 +1,12 @@ +country: India +leaders: + - github: leader-1-github + name: Leader 1 Name + - github: leader-2-github + name: Leader 2 Name +name: OWASP Chapter +tags: + - example-tag-1 + - example-tag-2 + - example-tag-3 +sponsors: [] diff --git a/schema/tests/data/chapter/negative/sponsors-name-missing.yaml b/schema/tests/data/chapter/negative/sponsors-name-missing.yaml new file mode 100644 index 000000000..824ee980a --- /dev/null +++ b/schema/tests/data/chapter/negative/sponsors-name-missing.yaml @@ -0,0 +1,15 @@ +country: India +leaders: + - github: leader-name-1 + name: Leader Name 1 + - github: leader-name-2 + name: Leader Name 2 +name: OWASP Chapter +tags: + - example-tag-1 + - example-tag-2 + - example-tag-3 +sponsors: + - description: A great sponsor + logo: https://sponsor1.com/logo.png + url: https://sponsor1.com diff --git a/schema/tests/data/chapter/negative/sponsors-url-missing.yaml b/schema/tests/data/chapter/negative/sponsors-url-missing.yaml new file mode 100644 index 000000000..79b98facc --- /dev/null +++ b/schema/tests/data/chapter/negative/sponsors-url-missing.yaml @@ -0,0 +1,15 @@ +country: India +leaders: + - github: leader-1-github + name: Leader 1 Name + - github: leader-2-github + name: Leader 2 Name +name: OWASP Chapter +tags: + - example-tag-1 + - example-tag-2 + - example-tag-3 +sponsors: + - description: A great sponsor + logo: https://sponsor1.com/logo.png + name: Sponsor 1 diff --git a/schema/tests/data/chapter/negative/website-none.yaml b/schema/tests/data/chapter/negative/website-none.yaml new file mode 100644 index 000000000..f9fa2a1c0 --- /dev/null +++ b/schema/tests/data/chapter/negative/website-none.yaml @@ -0,0 +1,8 @@ +country: India +leaders: + - github: leader-1-github + name: Leader 1 Name +name: OWASP Chapter +tags: + - example-tag-1 +website: diff --git a/schema/tests/data/chapter/positive/optional-properties.yaml b/schema/tests/data/chapter/positive/optional-properties.yaml new file mode 100644 index 000000000..35ddd3f3c --- /dev/null +++ b/schema/tests/data/chapter/positive/optional-properties.yaml @@ -0,0 +1,17 @@ +blog: https://blog.chapter.com +country: India +events: + - https://example.com/event1 + - https://example.com/event2 +leaders: + - github: leader-1-github + name: Leader 1 Name + - github: leader-2-github + name: Leader 2 Name + slack: leader-2-slack +name: OWASP Chapter +tags: + - example-tag-1 + - example-tag-2 + - example-tag-3 +website: https://chapter-website.com diff --git a/schema/tests/data/chapter/positive/required-properties.yaml b/schema/tests/data/chapter/positive/required-properties.yaml new file mode 100644 index 000000000..f128a1877 --- /dev/null +++ b/schema/tests/data/chapter/positive/required-properties.yaml @@ -0,0 +1,12 @@ +country: India +leaders: + - github: leader-1-github + name: Leader 1 Name + - github: leader-2-github + name: Leader 2 Name + slack: leader-2-slack +name: OWASP Chapter +tags: + - chapter-tag-1 + - chapter-tag-2 + - chapter-tag-3