From ca7c035ea56389283de7cd5af23422c7d8996e29 Mon Sep 17 00:00:00 2001 From: Joseph O'Brien Date: Wed, 8 Jul 2026 15:22:59 -0400 Subject: [PATCH] =?UTF-8?q?docs(handoff):=20log=202026-07-08=20=E2=80=94?= =?UTF-8?q?=20hexagonal=20architecture=20refactor,=20adapters,=20integrati?= =?UTF-8?q?on=20tests,=20notsecrets=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Completed hexagonal architecture refactor with adapter pattern - Added comprehensive integration tests - Cleaned up notsecrets integration - Moved config payload to notfiles-config repo Commits: 6427d18, 25624c9 --- .ctx/HANDOFF.notfiles.notfiles.yaml | 7 +- crates/notforge/tests/gitea.rs | 203 ++++++++++++++++++++++++++++ 2 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 crates/notforge/tests/gitea.rs diff --git a/.ctx/HANDOFF.notfiles.notfiles.yaml b/.ctx/HANDOFF.notfiles.notfiles.yaml index 0b9d98b..07eaa4a 100644 --- a/.ctx/HANDOFF.notfiles.notfiles.yaml +++ b/.ctx/HANDOFF.notfiles.notfiles.yaml @@ -1,6 +1,6 @@ project: notfiles id: notfile -updated: 2026-07-07 +updated: 2026-07-08 items: - id: uncommitted-work doob_uuid: null @@ -20,6 +20,11 @@ items: completed: null extra: [] log: +- date: 2026-07-08 + summary: hexagonal architecture refactor with adapters and integration tests, notsecrets cleanup, config payload moved to notfiles-config repo + commits: + - 6427d18 + - 25624c9 - date: '20260606.135918' summary: done=13 running=0 pending=0 blocked=0 commits: [] diff --git a/crates/notforge/tests/gitea.rs b/crates/notforge/tests/gitea.rs new file mode 100644 index 0000000..0ffa1e3 --- /dev/null +++ b/crates/notforge/tests/gitea.rs @@ -0,0 +1,203 @@ +use std::sync::{Arc, Mutex}; + +use notforge::config::RepoSpec; +use notforge::error::NotforgeError; +use notforge::gitea::{ + GiteaHttpApi, GiteaHttpRequest, GiteaHttpResponse, GiteaTransport, HttpMethod, +}; +use notforge::ports::{ForgeApi, ForgeAuth}; +use serde_json::json; + +#[derive(Debug, Clone)] +struct RecordedRequest { + method: HttpMethod, + url: String, + authorization: Option, + body: Option, +} + +#[derive(Clone)] +struct FakeTransport { + requests: Arc>>, + responses: Arc>>>, +} + +impl FakeTransport { + fn new(responses: Vec>) -> Self { + Self { + requests: Arc::new(Mutex::new(Vec::new())), + responses: Arc::new(Mutex::new(responses)), + } + } + + fn requests(&self) -> Vec { + self.requests.lock().unwrap().clone() + } +} + +impl GiteaTransport for FakeTransport { + fn send(&self, request: GiteaHttpRequest) -> Result { + self.requests.lock().unwrap().push(RecordedRequest { + method: request.method, + url: request.url, + authorization: request.authorization, + body: request.body, + }); + self.responses.lock().unwrap().remove(0) + } +} + +#[test] +fn version_gets_gitea_server_version() { + let transport = FakeTransport::new(vec![Ok(GiteaHttpResponse { + status: 200, + body: json!({ "version": "1.22.4" }), + })]); + let api = GiteaHttpApi::with_transport("http://gitea.local:3000/", transport.clone()); + + let version = api.version().unwrap(); + + assert_eq!(version.version, "1.22.4"); + let requests = transport.requests(); + assert_eq!(requests.len(), 1); + assert_eq!(requests[0].method, HttpMethod::Get); + assert_eq!(requests[0].url, "http://gitea.local:3000/api/v1/version"); + assert_eq!(requests[0].authorization, None); + assert_eq!(requests[0].body, None); +} + +#[test] +fn repo_maps_found_and_not_found_responses() { + let transport = FakeTransport::new(vec![ + Ok(GiteaHttpResponse { + status: 200, + body: json!({ + "owner": { "login": "joe" }, + "name": "notfiles-config", + "clone_url": "http://gitea.local:3000/joe/notfiles-config.git", + "ssh_url": "ssh://git@gitea.local:2222/joe/notfiles-config.git" + }), + }), + Ok(GiteaHttpResponse { + status: 404, + body: json!({ "message": "not found" }), + }), + ]); + let api = GiteaHttpApi::with_transport("http://gitea.local:3000", transport.clone()); + + let found = api.repo("joe", "notfiles-config").unwrap().unwrap(); + let missing = api.repo("joe", "missing").unwrap(); + + assert_eq!(found.owner, "joe"); + assert_eq!(found.name, "notfiles-config"); + assert_eq!( + found.clone_url, + "http://gitea.local:3000/joe/notfiles-config.git" + ); + assert_eq!( + found.ssh_url, + "ssh://git@gitea.local:2222/joe/notfiles-config.git" + ); + assert_eq!(missing, None); + + let requests = transport.requests(); + assert_eq!( + requests[0].url, + "http://gitea.local:3000/api/v1/repos/joe/notfiles-config" + ); + assert_eq!( + requests[1].url, + "http://gitea.local:3000/api/v1/repos/joe/missing" + ); +} + +#[test] +fn create_repo_posts_json_with_token_auth() { + let transport = FakeTransport::new(vec![Ok(GiteaHttpResponse { + status: 201, + body: json!({ + "owner": { "login": "joe" }, + "name": "notfiles-config", + "clone_url": "http://gitea.local:3000/joe/notfiles-config.git", + "ssh_url": "ssh://git@gitea.local:2222/joe/notfiles-config.git" + }), + })]); + let api = GiteaHttpApi::with_transport("http://gitea.local:3000", transport.clone()); + let spec = RepoSpec { + owner: "joe".to_string(), + name: "notfiles-config".to_string(), + private: true, + description: Some("Personal config payload".to_string()), + remote_name: "gitea".to_string(), + }; + + let repo = api + .create_repo( + &spec, + &ForgeAuth::Token { + token: "redacted".to_string(), + }, + ) + .unwrap(); + + assert_eq!(repo.owner, "joe"); + let requests = transport.requests(); + assert_eq!(requests.len(), 1); + assert_eq!(requests[0].method, HttpMethod::Post); + assert_eq!(requests[0].url, "http://gitea.local:3000/api/v1/user/repos"); + assert_eq!( + requests[0].authorization, + Some("token redacted".to_string()) + ); + assert_eq!( + requests[0].body, + Some(json!({ + "name": "notfiles-config", + "private": true, + "description": "Personal config payload" + })) + ); +} + +#[test] +fn create_repo_posts_json_with_basic_auth() { + let transport = FakeTransport::new(vec![Ok(GiteaHttpResponse { + status: 201, + body: json!({ + "owner": { "login": "joe" }, + "name": "notfiles-config", + "clone_url": "http://gitea.local:3000/joe/notfiles-config.git", + "ssh_url": "ssh://git@gitea.local:2222/joe/notfiles-config.git" + }), + })]); + let api = GiteaHttpApi::with_transport("http://gitea.local:3000", transport.clone()); + let spec = RepoSpec { + owner: "joe".to_string(), + name: "notfiles-config".to_string(), + private: true, + description: None, + remote_name: "gitea".to_string(), + }; + + api.create_repo( + &spec, + &ForgeAuth::Basic { + username: "joe".to_string(), + password: "secret".to_string(), + }, + ) + .unwrap(); + + let requests = transport.requests(); + assert_eq!( + requests[0].authorization, + Some("Basic am9lOnNlY3JldA==".to_string()) + ); + assert_eq!( + requests[0].body, + Some(json!({ + "name": "notfiles-config", + "private": true + })) + ); +}