1pub mod archive;
72pub mod contents;
73pub mod error;
74pub mod middleware;
75pub mod pagination;
76pub mod rate_limit;
77pub mod release;
78pub mod ssh_key;
79pub mod store;
80pub mod token;
81pub mod user;
82
83pub use archive::{create_archive, ArchiveEntry, ArchiveFormat, TarGzBuilder, ZipBuilder};
85pub use contents::{
86 base64_encode, detect_spdx_id, is_readme_file, recognize_license_file, ContentEntry,
87 ContentType, ContentsQuery, LicenseResponse, ReadmeResponse,
88};
89pub use error::{CompatError, Result};
90pub use middleware::{
91 parse_authorization_header, resource_from_path, AuthContext, AuthorizationValue, ErrorResponse,
92 ResponseHeaders, ValidationError, ValidationErrorCode,
93};
94pub use pagination::{
95 paginate, PaginatedResponse, PaginationLinks, PaginationParams, DEFAULT_PER_PAGE, MAX_PER_PAGE,
96};
97pub use rate_limit::{
98 RateLimitHeaders, RateLimitInfo, RateLimitResource, RateLimitResources, RateLimitResponse,
99 RateLimitState, RateLimiter, DEFAULT_RATE_LIMIT, UNAUTHENTICATED_RATE_LIMIT,
100};
101pub use release::{
102 AssetId, AssetResponse, AuthorInfo, CreateReleaseRequest, Release, ReleaseAsset, ReleaseId,
103 ReleaseResponse, UpdateReleaseRequest,
104};
105pub use ssh_key::{AddSshKeyRequest, SshKey, SshKeyId, SshKeyResponse, SshKeyType};
106pub use store::{CompatStats, CompatStore, ReleaseStore, SshKeyStore, TokenStore, UserStore};
107pub use token::{
108 CreateTokenRequest, PersonalAccessToken, TokenId, TokenResponse, TokenScope, TokenValue,
109};
110pub use user::{CreateUserRequest, UpdateUserRequest, User, UserId, UserProfile};
111
112pub const VERSION: &str = env!("CARGO_PKG_VERSION");
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn test_public_api() {
121 let store = CompatStore::new();
123 assert_eq!(store.users.count(), 0);
124 assert_eq!(store.tokens.count(), 0);
125 }
126
127 #[test]
128 fn test_full_user_token_flow() {
129 let store = CompatStore::new();
130
131 let user = store
133 .users
134 .create("alice".to_string(), "pubkey123".to_string())
135 .unwrap();
136 assert_eq!(user.username, "alice");
137
138 let (token, plaintext) = store
140 .tokens
141 .create(
142 user.id,
143 "My Token".to_string(),
144 vec![TokenScope::RepoRead, TokenScope::RepoWrite],
145 None,
146 )
147 .unwrap();
148
149 assert!(plaintext.starts_with("guts_"));
150
151 let (user_id, scopes) = store.tokens.verify(&plaintext).unwrap();
153 assert_eq!(user_id, user.id);
154 assert!(scopes.contains(&TokenScope::RepoRead));
155 assert!(scopes.contains(&TokenScope::RepoWrite));
156
157 assert!(token.has_scope(TokenScope::RepoRead));
159 assert!(token.has_scope(TokenScope::RepoWrite));
160 assert!(!token.has_scope(TokenScope::Admin));
161 }
162
163 #[test]
164 fn test_rate_limiting() {
165 let limiter = RateLimiter::new();
166
167 let state = limiter.get_state("user1", RateLimitResource::Core, true);
169 assert_eq!(state.limit, 5000);
170
171 let state = limiter.get_state("anon", RateLimitResource::Core, false);
173 assert_eq!(state.limit, 60);
174 }
175
176 #[test]
177 fn test_pagination() {
178 let items: Vec<i32> = (1..=100).collect();
179 let params = PaginationParams::new(2, 10);
180 let response = paginate(&items, ¶ms);
181
182 assert_eq!(response.items.len(), 10);
183 assert_eq!(response.total_count, 100);
184 assert_eq!(response.page, 2);
185 assert!(response.has_next_page());
186 assert!(response.has_prev_page());
187 }
188
189 #[test]
190 fn test_release_management() {
191 let store = CompatStore::new();
192
193 let release = store
195 .releases
196 .create(
197 "alice/repo".to_string(),
198 "v1.0.0".to_string(),
199 "main".to_string(),
200 "alice".to_string(),
201 )
202 .unwrap();
203
204 let asset = store
206 .releases
207 .add_asset(
208 release.id,
209 "app-linux-amd64.tar.gz".to_string(),
210 "application/gzip".to_string(),
211 b"binary content".to_vec(),
212 "alice".to_string(),
213 )
214 .unwrap();
215
216 assert_eq!(asset.name, "app-linux-amd64.tar.gz");
217
218 let content = store.releases.get_asset_content(&asset.content_hash);
220 assert!(content.is_some());
221 }
222
223 #[test]
224 fn test_archive_generation() {
225 let entries = vec![
226 ArchiveEntry::file("README.md".to_string(), b"# My Project".to_vec()),
227 ArchiveEntry::file("src/main.rs".to_string(), b"fn main() {}".to_vec()),
228 ];
229
230 let archive = create_archive(
231 ArchiveFormat::TarGz,
232 "my-project-v1.0.0".to_string(),
233 entries,
234 );
235 assert!(archive.is_ok());
236 }
237
238 #[test]
239 fn test_ssh_key_management() {
240 let store = CompatStore::new();
241
242 let key = store.ssh_keys.add(
244 1,
245 "My Laptop".to_string(),
246 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl user@laptop".to_string(),
247 ).unwrap();
248
249 assert!(key.fingerprint.starts_with("SHA256:"));
250 assert_eq!(key.key_type, SshKeyType::Ed25519);
251
252 let keys = store.ssh_keys.list_for_user(1);
254 assert_eq!(keys.len(), 1);
255 }
256}