diff --git a/forgejo-hooks/Cargo.toml b/forgejo-hooks/Cargo.toml index 0cf8bce..8bfee68 100644 --- a/forgejo-hooks/Cargo.toml +++ b/forgejo-hooks/Cargo.toml @@ -7,3 +7,7 @@ license = "AGPL-3.0+" [dependencies] chrono = { version = "0.4", default-features = false, features = ["serde"] } serde = { version = "1.0", features = ["derive"] } + +[dev-dependencies] +serde_json = "1.0" +serde_path_to_error = "0.1.16" diff --git a/forgejo-hooks/src/lib.rs b/forgejo-hooks/src/lib.rs index 227ef85..0016ff5 100644 --- a/forgejo-hooks/src/lib.rs +++ b/forgejo-hooks/src/lib.rs @@ -13,17 +13,20 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +#[cfg(test)] +mod tests; + use chrono::{DateTime, Utc}; use serde::Deserialize; -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, PartialEq)] pub struct CommitAuthor { pub name: String, pub email: String, pub username: String, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, PartialEq)] pub struct User { pub id: u32, pub login: String, @@ -33,7 +36,7 @@ pub struct User { pub username: String, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, PartialEq)] pub struct Commit { pub id: String, pub message: String, @@ -43,21 +46,21 @@ pub struct Commit { pub timestamp: DateTime, } -#[derive(Deserialize, Debug)] -pub struct Perms { +#[derive(Deserialize, Debug, PartialEq)] +pub struct Permissions { admin: bool, push: bool, pull: bool, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, PartialEq)] pub struct InternalTracker { enable_time_tracker: bool, allow_only_contributors_to_track_time: bool, enable_issue_dependencies: bool, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, PartialEq)] pub struct Repository { pub id: u32, pub owner: User, @@ -79,7 +82,7 @@ pub struct Repository { pub updated_at: DateTime, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, PartialEq)] pub struct RepositoryFull { pub id: u32, pub owner: RepositoryOwner, @@ -134,7 +137,7 @@ pub struct RepositoryFull { pub repo_transfer: Option, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, PartialEq)] pub struct RepositoryOwner { pub id: u32, pub login: String, @@ -159,7 +162,7 @@ pub struct RepositoryOwner { pub username: String, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, PartialEq)] pub struct Push { #[serde(rename(deserialize = "ref"))] pub ref_: String, @@ -171,12 +174,12 @@ pub struct Push { pub sender: User, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, PartialEq)] pub enum RefType { Branch, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, PartialEq)] pub struct AddedBranch { pub sha: String, pub ref_type: String, @@ -184,7 +187,7 @@ pub struct AddedBranch { pub sender: RepositoryOwner, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, PartialEq)] pub struct RemovedBranch { pub sha: String, pub ref_type: String, @@ -193,7 +196,19 @@ pub struct RemovedBranch { pub sender: RepositoryOwner, } -#[derive(Deserialize, Debug)] +impl From for Hook { + fn from(push: Push) -> Self { + Self::Push(push) + } +} + +impl From for Hook { + fn from(issue: Issue) -> Self { + Self::Issue(issue) + } +} + +#[derive(Deserialize, Debug, PartialEq)] #[serde(untagged)] pub enum Hook { Push(Push), diff --git a/forgejo-hooks/src/tests/mod.rs b/forgejo-hooks/src/tests/mod.rs new file mode 100644 index 0000000..00e32cc --- /dev/null +++ b/forgejo-hooks/src/tests/mod.rs @@ -0,0 +1,54 @@ +// Copyright (C) 2024-2099 The crate authors. +// +// 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 . + +use crate::{Hook, Push}; +use serde::Deserialize; + +/// Deserializes the payload into a struct +fn parse(payload: &str) -> Result> +where + for<'a> Output: Deserialize<'a>, +{ + let mut json = serde_json::Deserializer::from_str(payload); + serde_path_to_error::deserialize(&mut json) +} + +/// Tries to parse once into a specific type, then parses into Hook and ensure both match +fn parse_roundtrip(payload: &str) -> () +where + for<'a> Output: Into + Deserialize<'a> + PartialEq, +{ + let res1: Result = parse(&payload); + match res1 { + Ok(_) => (), + Err(err) => panic!("Error: {err:?}"), + } + + let res2: Result = parse(&payload); + match res2 { + Ok(_) => (), + Err(err) => panic!("Error: {err:?}"), + } + + let hook1: Output = res1.unwrap(); + let hook2: Hook = res2.unwrap(); + assert_eq!(Into::::into(hook1), hook2); +} + +#[test] +fn push_payload() { + let payload = std::fs::read_to_string("src/tests/push.json").unwrap(); + let _ = parse_roundtrip::(&payload); +} diff --git a/forgejo-hooks/src/tests/push.json b/forgejo-hooks/src/tests/push.json new file mode 100644 index 0000000..f37c925 --- /dev/null +++ b/forgejo-hooks/src/tests/push.json @@ -0,0 +1,198 @@ +{ + "ref": "refs/heads/main", + "before": "28cb105298bd70d268a1f1e7ff7f2b20ab9f1dc6", + "after": "76d3676e2516a20ee3a3819175594cf10b75fa91", + "compare_url": "https://code.bouah.net/pep/webhook-test/compare/28cb105298bd70d268a1f1e7ff7f2b20ab9f1dc6...76d3676e2516a20ee3a3819175594cf10b75fa91", + "commits": [{ + "id": "76d3676e2516a20ee3a3819175594cf10b75fa91", + "message": "bar\\n\\nSigned-off-by: pep \\u003cpep@bouah.net\\u003e\\n", + "url": "https://code.bouah.net/pep/webhook-test/commit/76d3676e2516a20ee3a3819175594cf10b75fa91", + "author": { + "name": "pep", + "email": "pep@bouah.net", + "username": "pep" + }, + "committer": { + "name": "pep", + "email": "pep@bouah.net", + "username": "pep" + }, + "verification": null, + "timestamp": "2024-09-01T16:49:08+02:00", + "added": [], + "removed": [], + "modified": [ + "foo" + ] + }], + "total_commits": 1, + "head_commit": { + "id": "76d3676e2516a20ee3a3819175594cf10b75fa91", + "message": "bar\\n\\nSigned-off-by: pep \\u003cpep@bouah.net\\u003e\\n", + "url": "https://code.bouah.net/pep/webhook-test/commit/76d3676e2516a20ee3a3819175594cf10b75fa91", + "author": { + "name": "pep", + "email": "pep@bouah.net", + "username": "pep" + }, + "committer": { + "name": "pep", + "email": "pep@bouah.net", + "username": "pep" + }, + "verification": null, + "timestamp": "2024-09-01T16:49:08+02:00", + "added": [], + "removed": [], + "modified": [ + "foo" + ] + }, + "repository": { + "id": 20, + "owner": { + "id": 1, + "login": "pep", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "pep@bouah.net", + "avatar_url": "https://code.bouah.net/avatars/7897535c1d4a7ed71eb7b9bc8773bed6", + "html_url": "https://code.bouah.net/pep", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2023-01-02T21:57:48Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "https://bouah.net", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "pep" + }, + "name": "webhook-test", + "full_name": "pep/webhook-test", + "description": "", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 28, + "language": "", + "languages_url": "https://code.bouah.net/api/v1/repos/pep/webhook-test/languages", + "html_url": "https://code.bouah.net/pep/webhook-test", + "url": "https://code.bouah.net/api/v1/repos/pep/webhook-test", + "link": "", + "ssh_url": "forgejo@code.bouah.net:pep/webhook-test.git", + "clone_url": "https://code.bouah.net/pep/webhook-test.git", + "original_url": "", + "website": "", + "stars_count": 0, + "forks_count": 0, + "watchers_count": 1, + "open_issues_count": 5, + "open_pr_counter": 0, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2024-04-23T11:49:34Z", + "updated_at": "2024-04-23T17:42:53Z", + "archived_at": "1970-01-01T00:00:00Z", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": false, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": true, + "wiki_branch": "master", + "globally_editable_wiki": false, + "has_pull_requests": true, + "has_projects": true, + "has_releases": true, + "has_packages": true, + "has_actions": false, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": false, + "allow_rebase_update": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "rebase", + "default_allow_maintainer_edit": false, + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha1", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null, + "topics": null + }, + "pusher": { + "id": 1, + "login": "pep", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "pep@forgejo-noreply", + "avatar_url": "https://code.bouah.net/avatars/7897535c1d4a7ed71eb7b9bc8773bed6", + "html_url": "https://code.bouah.net/pep", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2023-01-02T21:57:48Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "https://bouah.net", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "pep" + }, + "sender": { + "id": 1, + "login": "pep", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "pep@forgejo-noreply", + "avatar_url": "https://code.bouah.net/avatars/7897535c1d4a7ed71eb7b9bc8773bed6", + "html_url": "https://code.bouah.net/pep", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2023-01-02T21:57:48Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "https://bouah.net", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "pep" + } +}