diff --git a/activitypub-servist.asd b/activitypub-servist.asd index 9848eb3..f364aad 100644 --- a/activitypub-servist.asd +++ b/activitypub-servist.asd @@ -14,6 +14,17 @@ :components ((:file "src/activitypub-servist"))) +(asdf:defsystem "activitypub-servist/activity-vocabulary" + :version "0.0" + :license "AGPLv3" + :description "AP-S subpackage for handling ActivityVocabulary parsing/encoding." + :author "Jaidyn Ann " + :homepage "https://hak.xwx.moe/jadedctrl/activitypub-servist" + + :depends-on ("alexandria" "closer-mop" "str" "yason") + :components ((:file "src/activity-vocabulary"))) + + (asdf:defsystem "activitypub-servist/signatures" :version "0.0" :license "AGPLv3" @@ -28,6 +39,16 @@ ;;; Tests ;;; ————————————————————————————————————— +(asdf:defsystem "activitypub-servist/tests/activity-vocabulary" + :version "0.0" + :license "AGPLv3" + :author "Jaidyn Ann " + :description "Tests for the the activitypub-servist/signatures package." + + :depends-on (:activitypub-servist/activity-vocabulary :alexandria :lisp-unit2) + :components ((:file "t/activity-vocabulary"))) + + (asdf:defsystem "activitypub-servist/tests/signatures" :version "0.0" :license "AGPLv3" @@ -37,10 +58,11 @@ :depends-on (:activitypub-servist/signatures :lisp-unit2) :components ((:file "t/signatures"))) + ;; Following method tweaked from lisp-unit2’s documentation: ;; https://github.com/AccelerationNet/lisp-unit2/blob/master/README.md#asdf -(defmethod asdf:perform - ((o asdf:test-op) (c (eql (asdf:find-system :activitypub-servist/tests/signatures)))) - (eval (read-from-string - "(lisp-unit2:with-summary () - (lisp-unit2:run-tests :package :activitypub-servist/tests/signatures))"))) +(defmethod asdf:perform ((o asdf:test-op) (c (eql (asdf:find-system :activitypub-servist/tests/activity-vocabulary)))) + (eval (read-from-string "(activtiypub-servist/tests/activity-vocabulary:run)"))) + +(defmethod asdf:perform ((o asdf:test-op) (c (eql (asdf:find-system :activitypub-servist/tests/signatures)))) + (eval (read-from-string "(activtiypub-servist/tests/signatures:run)"))) diff --git a/t/activity-vocabulary.lisp b/t/activity-vocabulary.lisp new file mode 100644 index 0000000..7de47d3 --- /dev/null +++ b/t/activity-vocabulary.lisp @@ -0,0 +1,81 @@ +;;;; activitypub-servist/tests/activity-vocabulary: Testing activity-vocabulary. + +;; Copyright © 2024 Jaidyn Levesque +;; +;; This program is free software: you can redistribute it and/or +;; modify it under the terms of the GNU Affero General Public License +;; as published by the Free Software Foundation, either version 3 of +;; the License, or (at your option) any later version. +;; +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU Affero General Public License for more details. +;; +;; You should have received a copy of the GNU Affero General Public License +;; along with this program. If not, see . + +(defpackage :activitypub-servist/tests/activity-vocabulary + (:use :cl :lisp-unit2) + (:nicknames "AP-S/T/AV") + (:export :run)) + +(in-package :activitypub-servist/tests/activity-vocabulary) + +(defun run () + "Run all ACTIVITY-VOCABULARY tests." + (lisp-unit2:with-summary () + (lisp-unit2:run-tests :package :activitypub-servist/tests/activity-vocabulary))) + + + +;;; Util +;;; ———————————————————————————————————————— +(defmacro relative-pathname (path) + "Return an absolute path adding the relative PATH to the system’s path." + `(asdf:system-relative-pathname :activitypub-servist/tests/activity-vocabulary ,path)) + +(defmacro define-json-test (path tags) + "Define a lisp-unit2 test for parsing of the given JSON file. +We compare the original JSON to that of the parsed-then-reserialized JSON, +ensuring they are semantically equivalent. White-space and key order are ignored." + (let ((content (alexandria:read-file-into-string (relative-pathname path)))) + `(define-test ,(intern (string-upcase (pathname-name path))) (:tags ,tags) + (assert-equal + (hash-table-sorted-alist + (yason:parse ,content)) + (hash-table-sorted-alist + (yason:parse + (yason:with-output-to-string* () + (yason:encode-object + (av:parse ,content))))))))) + +(defun sort-alist (alist predicate) + "Sort an associative list by its keys." + (sort alist + (lambda (cell-a cell-b) + (apply predicate (list (car cell-a) (car cell-b)))))) + +(defun hash-table-sorted-alist (table &optional (predicate #'string<)) + "Return a sorted associative list containing the keys and values of TABLE. +Any nested hash-tables found as values are also sorted, recursively." + (sort-alist + (mapcar (lambda (cell) + (cons (car cell) + (if (hash-table-p (cdr cell)) + (hash-table-sorted-alist (cdr cell)) + (cdr cell)))) + (alexandria:hash-table-alist table)) + predicate)) + + + +;;; Test definitions +;;; ———————————————————————————————————————— +;; Define a test for each ActivityVocabulary type’s example JSON. +;; Examples are taken from the spec: +;; https://www.w3.org/TR/activitystreams-vocabulary/ +(mapcar (lambda (file) + (eval `(define-json-test ,file '(:core)))) + (uiop:directory-files + (relative-pathname "t/activity-vocabulary/core/"))) diff --git a/t/activity-vocabulary/core/activity.json b/t/activity-vocabulary/core/activity.json new file mode 100644 index 0000000..5b03c30 --- /dev/null +++ b/t/activity-vocabulary/core/activity.json @@ -0,0 +1,13 @@ +{ + "@context": "https://www.w3.org/ns/activitystreams", + "type": "Activity", + "summary": "Sally did something to a note", + "actor": { + "type": "Person", + "name": "Sally" + }, + "object": { + "type": "Note", + "name": "A Note" + } +} diff --git a/t/activity-vocabulary/core/collection-page.json b/t/activity-vocabulary/core/collection-page.json new file mode 100644 index 0000000..a3abab4 --- /dev/null +++ b/t/activity-vocabulary/core/collection-page.json @@ -0,0 +1,17 @@ +{ + "@context": "https://www.w3.org/ns/activitystreams", + "summary": "Page 1 of Sally's notes", + "type": "CollectionPage", + "id": "http://example.org/foo?page=1", + "partOf": "http://example.org/foo", + "items": [ + { + "type": "Note", + "name": "A Simple Note" + }, + { + "type": "Note", + "name": "Another Simple Note" + } + ] +} diff --git a/t/activity-vocabulary/core/collection.json b/t/activity-vocabulary/core/collection.json new file mode 100644 index 0000000..32a4b2d --- /dev/null +++ b/t/activity-vocabulary/core/collection.json @@ -0,0 +1,16 @@ +{ + "@context": "https://www.w3.org/ns/activitystreams", + "summary": "Sally's notes", + "type": "Collection", + "totalItems": 2, + "items": [ + { + "type": "Note", + "name": "A Simple Note" + }, + { + "type": "Note", + "name": "Another Simple Note" + } + ] +} diff --git a/t/activity-vocabulary/core/intransitive-activity.json b/t/activity-vocabulary/core/intransitive-activity.json new file mode 100644 index 0000000..d02cd77 --- /dev/null +++ b/t/activity-vocabulary/core/intransitive-activity.json @@ -0,0 +1,13 @@ +{ + "@context": "https://www.w3.org/ns/activitystreams", + "type": "Travel", + "summary": "Sally went to work", + "actor": { + "type": "Person", + "name": "Sally" + }, + "target": { + "type": "Place", + "name": "Work" + } +} diff --git a/t/activity-vocabulary/core/link.json b/t/activity-vocabulary/core/link.json new file mode 100644 index 0000000..acc7490 --- /dev/null +++ b/t/activity-vocabulary/core/link.json @@ -0,0 +1,8 @@ +{ + "@context": "https://www.w3.org/ns/activitystreams", + "type": "Link", + "href": "http://example.org/abc", + "hreflang": "en", + "mediaType": "text/html", + "name": "An example link" +} diff --git a/t/activity-vocabulary/core/object.json b/t/activity-vocabulary/core/object.json new file mode 100644 index 0000000..8bd7c51 --- /dev/null +++ b/t/activity-vocabulary/core/object.json @@ -0,0 +1,6 @@ +{ + "@context": "https://www.w3.org/ns/activitystreams", + "type": "Object", + "id": "http://www.test.example/object/1", + "name": "A Simple, non-specific object" +} diff --git a/t/activity-vocabulary/core/ordered-collection-page.json b/t/activity-vocabulary/core/ordered-collection-page.json new file mode 100644 index 0000000..0e45a01 --- /dev/null +++ b/t/activity-vocabulary/core/ordered-collection-page.json @@ -0,0 +1,17 @@ +{ + "@context": "https://www.w3.org/ns/activitystreams", + "summary": "Page 1 of Sally's notes", + "type": "OrderedCollectionPage", + "id": "http://example.org/foo?page=1", + "partOf": "http://example.org/foo", + "orderedItems": [ + { + "type": "Note", + "name": "A Simple Note" + }, + { + "type": "Note", + "name": "Another Simple Note" + } + ] +} diff --git a/t/activity-vocabulary/core/ordered-collection.json b/t/activity-vocabulary/core/ordered-collection.json new file mode 100644 index 0000000..a7f456a --- /dev/null +++ b/t/activity-vocabulary/core/ordered-collection.json @@ -0,0 +1,16 @@ +{ + "@context": "https://www.w3.org/ns/activitystreams", + "summary": "Sally's notes", + "type": "OrderedCollection", + "totalItems": 2, + "orderedItems": [ + { + "type": "Note", + "name": "A Simple Note" + }, + { + "type": "Note", + "name": "Another Simple Note" + } + ] +} diff --git a/t/signatures.lisp b/t/signatures.lisp index 6394960..cc92812 100644 --- a/t/signatures.lisp +++ b/t/signatures.lisp @@ -16,11 +16,21 @@ ;; along with this program. If not, see . (defpackage :activitypub-servist/tests/signatures - (:use :cl :lisp-unit2)) + (:use :cl :lisp-unit2) + (:nicknames "AP-S/T/S") + (:export :run)) (in-package :activitypub-servist/tests/signatures) +(defun run () + "Run all SIGNATURES tests." + (lisp-unit2:with-summary () + (lisp-unit2:run-tests :package :activitypub-servist/tests/signatures))) + + +;;; Test definitions +;;; ———————————————————————————————————————— (define-test string-sha256sum (:tags '(misc)) (assert-equal "erws/VxJ7XO5xQBqpwHIUwG0P4q1Ek2D4N053+E2Ib8="