Compare commits
21
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a59fff032
|
||
|
|
d9089afdc9
|
||
|
|
59a81773cc
|
||
|
|
f7957ab9f5
|
||
|
|
42829ce0a0
|
||
|
|
bc3f4c4ec6
|
||
|
|
c3d5ed8b26
|
||
|
|
a7c0c25b26
|
||
|
|
08cca0ca51
|
||
|
|
2f61e15646
|
||
|
|
5e5e3f5b17
|
||
|
|
f04c9611b6
|
||
|
|
14a1c4944a
|
||
|
|
c743f51526
|
||
|
|
f4160df1d7
|
||
|
|
3f5f18d98f
|
||
|
|
85e9046768
|
||
|
|
5728649c79
|
||
|
|
8eb0048606
|
||
|
|
d92cb57d87
|
||
|
|
884f2dfec1
|
@@ -0,0 +1,25 @@
|
||||
name: Renovate
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 4 * * 1"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
renovate:
|
||||
name: Dependency-Updates prüfen & Pull Requests erstellen
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/renovatebot/renovate:43.279.0
|
||||
steps:
|
||||
- name: Renovate ausführen
|
||||
run: renovate
|
||||
env:
|
||||
RENOVATE_PLATFORM: gitea
|
||||
RENOVATE_ENDPOINT: ${{ gitea.server_url || github.server_url }}/api/v1/
|
||||
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
|
||||
RENOVATE_REPOSITORIES: ${{ gitea.repository || github.repository }}
|
||||
RENOVATE_AUTODISCOVER: "false"
|
||||
RENOVATE_GIT_AUTHOR: "Renovate Bot <renovate-bot@creative-dragonslayer.de>"
|
||||
RENOVATE_HOST_RULES: >-
|
||||
[{"hostType":"cargo","matchHost":"gitea.creative-dragonslayer.de","token":"${{ secrets.RENOVATE_TOKEN }}"}]
|
||||
LOG_LEVEL: info
|
||||
@@ -109,3 +109,4 @@ fabric.properties
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
.junie/plans
|
||||
|
||||
Generated
+3
@@ -17,6 +17,9 @@
|
||||
<Item>
|
||||
<option name="path" value=".gitea/workflows/testing.yaml" />
|
||||
</Item>
|
||||
<Item>
|
||||
<option name="path" value=".gitea/workflows/renovate.yaml" />
|
||||
</Item>
|
||||
</list>
|
||||
</option>
|
||||
</SchemaInfo>
|
||||
|
||||
Generated
+20
@@ -0,0 +1,20 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Run" type="CargoCommandRunConfiguration" factoryName="Cargo Command" nameIsGenerated="true">
|
||||
<option name="buildProfileId" value="dev" />
|
||||
<option name="command" value="run" />
|
||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||
<envs />
|
||||
<option name="emulateTerminal" value="true" />
|
||||
<option name="channel" value="DEFAULT" />
|
||||
<option name="requiredFeatures" value="true" />
|
||||
<option name="allFeatures" value="false" />
|
||||
<option name="withSudo" value="false" />
|
||||
<option name="buildTarget" value="REMOTE" />
|
||||
<option name="backtrace" value="SHORT" />
|
||||
<option name="isRedirectInput" value="false" />
|
||||
<option name="redirectInputPath" value="" />
|
||||
<method v="2">
|
||||
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
Generated
+1
@@ -4,6 +4,7 @@
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.junie/plans" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
|
||||
Generated
+1
-2
@@ -866,7 +866,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mirror-package"
|
||||
version = "1.0.0"
|
||||
version = "1.0.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -874,7 +874,6 @@ dependencies = [
|
||||
"logger-ctdra",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
|
||||
+1
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mirror-package"
|
||||
version = "1.0.0"
|
||||
version = "1.0.2"
|
||||
edition = "2024"
|
||||
authors = ['DragonSlayer_14']
|
||||
readme = "README.md"
|
||||
@@ -15,7 +15,6 @@ clap = { version = "4.6.6", features = ["derive", "env"] }
|
||||
reqwest = { version = "0.13.4", features = ["json", "stream"] }
|
||||
tokio = { version = "1.53.1", features = ["full"] }
|
||||
serde = { version = "1.0.229", features = ["derive"] }
|
||||
serde_json = "1.0.151"
|
||||
anyhow = "1.0.104"
|
||||
|
||||
[profile.release]
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
# Minimales und gehärtetes Runtime-Image
|
||||
FROM debian:bookworm-slim AS runtime
|
||||
FROM debian:trixie-slim AS runtime
|
||||
|
||||
# CA-Zertifikate und minimale dynamische Laufzeitbibliotheken installieren
|
||||
RUN apt-get update && \
|
||||
|
||||
@@ -120,7 +120,7 @@ Der Container wurde nach höchsten Sicherheitsstandards aufgebaut:
|
||||
- **Read-Only Root-Dateisystem**: Voll funktionsfähig mit `--read-only` / `read_only: true`.
|
||||
- **Keine Capabilities**: Sämtliche Linux-Capabilities können sicher entzogen werden (`--cap-drop=ALL`).
|
||||
- **Keine Rechteausweitung**: Erzwingt `no-new-privileges:true`.
|
||||
- **Minimale Image-Größe**: Basiert auf Debian Bookworm Slim und enthält nur CA-Zertifikate und notwendige dynamische Bibliotheken (~40 MB).
|
||||
- **Minimale Image-Größe**: Basiert auf Debian Trixie Slim und enthält nur CA-Zertifikate und notwendige dynamische Bibliotheken (~40 MB).
|
||||
|
||||
### 4. Ausführung über Docker CLI
|
||||
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
#-------------------------------------------------------------------------------#
|
||||
# Qodana analysis is configured by qodana.yaml file #
|
||||
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
|
||||
#-------------------------------------------------------------------------------#
|
||||
|
||||
#################################################################################
|
||||
# WARNING: Do not store sensitive information in this file, #
|
||||
# as its contents will be included in the Qodana report. #
|
||||
#################################################################################
|
||||
version: "1.0"
|
||||
|
||||
#Specify inspection profile for code analysis
|
||||
profile:
|
||||
name: qodana.starter
|
||||
|
||||
#Enable inspections
|
||||
#include:
|
||||
# - name: <SomeEnabledInspectionId>
|
||||
|
||||
#Disable inspections
|
||||
#exclude:
|
||||
# - name: <SomeDisabledInspectionId>
|
||||
# paths:
|
||||
# - <path/where/not/run/inspection>
|
||||
|
||||
#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
|
||||
#bootstrap: sh ./prepare-qodana.sh
|
||||
|
||||
#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
|
||||
#plugins:
|
||||
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
|
||||
|
||||
# Quality gate. Will fail the CI/CD pipeline if any condition is not met
|
||||
# severityThresholds - configures maximum thresholds for different problem severities
|
||||
# testCoverageThresholds - configures minimum code coverage on a whole project and newly added code
|
||||
# dependencyLicenses - fails the run on prohibited or unknown dependency licenses
|
||||
# Code Coverage is available in Ultimate and Ultimate Plus plans
|
||||
#failureConditions:
|
||||
# severityThresholds:
|
||||
# any: 15
|
||||
# critical: 5
|
||||
# testCoverageThresholds:
|
||||
# fresh: 70
|
||||
# total: 50
|
||||
# dependencyLicenses:
|
||||
# failOnProhibited: true
|
||||
# failOnUnknown: false
|
||||
|
||||
#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
|
||||
linter: jetbrains/qodana-<linter>:2026.2
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:recommended"],
|
||||
"timezone": "Europe/Berlin",
|
||||
"schedule": ["before 6am on monday"],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchManagers": ["github-actions"],
|
||||
"groupName": "Gitea Actions"
|
||||
},
|
||||
{
|
||||
"matchManagers": ["dockerfile", "docker-compose"],
|
||||
"groupName": "Docker-Images"
|
||||
}
|
||||
]
|
||||
}
|
||||
+44
-16
@@ -1,3 +1,4 @@
|
||||
use crate::utils::{clean_repo_input, sanitize_string};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -21,7 +22,7 @@ pub struct RepoConfig {
|
||||
impl RepoConfig {
|
||||
pub fn new(name: impl Into<String>, include_prereleases: bool) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
name: AppConfig::normalize_repo_name(&name.into()),
|
||||
include_prereleases,
|
||||
last_synced_tag: None,
|
||||
}
|
||||
@@ -49,21 +50,17 @@ pub struct AppConfig {
|
||||
}
|
||||
|
||||
impl AppConfig {
|
||||
/// Normalisiert die Repository-Eingabe, indem führende/nachgestellte Schrägstriche und GitHub-URL-Präfixe entfernt werden.
|
||||
/// Normalisiert die Repository-Eingabe, indem führende/nachgestellte Schrägstriche,
|
||||
/// Query-Parameter, Fragmente und GitHub-URL-Präfixe entfernt werden.
|
||||
pub fn normalize_repo_name(input: &str) -> String {
|
||||
let trimmed = input.trim();
|
||||
let cleaned = trimmed
|
||||
.trim_start_matches("https://github.com/")
|
||||
.trim_start_matches("http://github.com/")
|
||||
.trim_start_matches("github.com/")
|
||||
.trim_end_matches(".git")
|
||||
.trim_matches('/');
|
||||
cleaned.to_string()
|
||||
clean_repo_input(input).to_string()
|
||||
}
|
||||
|
||||
/// Fügt ein Repository zur Konfiguration hinzu oder aktualisiert ein bestehendes.
|
||||
pub fn add_or_update_repo(&mut self, repo: RepoConfig) {
|
||||
if let Some(existing) = self.repositories.iter_mut().find(|r| r.name.eq_ignore_ascii_case(&repo.name)) {
|
||||
pub fn add_or_update_repo(&mut self, mut repo: RepoConfig) {
|
||||
repo.name = Self::normalize_repo_name(&repo.name);
|
||||
if let Some(existing) = self.repositories.iter_mut().find(|r| Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&repo.name)) {
|
||||
existing.name = repo.name;
|
||||
existing.include_prereleases = repo.include_prereleases;
|
||||
} else {
|
||||
self.repositories.push(repo);
|
||||
@@ -74,20 +71,20 @@ impl AppConfig {
|
||||
pub fn remove_repo(&mut self, repo_name: &str) -> bool {
|
||||
let normalized = Self::normalize_repo_name(repo_name);
|
||||
let before_len = self.repositories.len();
|
||||
self.repositories.retain(|r| !r.name.eq_ignore_ascii_case(&normalized));
|
||||
self.repositories.retain(|r| !Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&normalized));
|
||||
self.repositories.len() < before_len
|
||||
}
|
||||
|
||||
/// Sucht ein Repository anhand des Namens.
|
||||
pub fn find_repo(&self, repo_name: &str) -> Option<&RepoConfig> {
|
||||
let normalized = Self::normalize_repo_name(repo_name);
|
||||
self.repositories.iter().find(|r| r.name.eq_ignore_ascii_case(&normalized))
|
||||
self.repositories.iter().find(|r| Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&normalized))
|
||||
}
|
||||
|
||||
/// Aktualisiert den zuletzt synchronisierten Tag für ein bestimmtes Repository.
|
||||
pub fn update_last_synced_tag(&mut self, repo_name: &str, tag: String) {
|
||||
let normalized = Self::normalize_repo_name(repo_name);
|
||||
if let Some(repo) = self.repositories.iter_mut().find(|r| r.name.eq_ignore_ascii_case(&normalized)) {
|
||||
if let Some(repo) = self.repositories.iter_mut().find(|r| Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&normalized)) {
|
||||
repo.last_synced_tag = Some(tag);
|
||||
}
|
||||
}
|
||||
@@ -108,7 +105,38 @@ pub fn get_config_file_path() -> PathBuf {
|
||||
|
||||
/// Lädt die Anwendungskonfiguration. Verwendet Standardwerte, falls keine Datei vorhanden ist.
|
||||
pub fn load_config() -> AppConfig {
|
||||
config_ctdra::load_config::<AppConfig>()
|
||||
let mut config = config_ctdra::load_config::<AppConfig>();
|
||||
|
||||
for repo in &mut config.repositories {
|
||||
repo.name = AppConfig::normalize_repo_name(&repo.name);
|
||||
}
|
||||
|
||||
if let Ok(val) = std::env::var("GITEA_URL") {
|
||||
let val = sanitize_string(&val);
|
||||
if !val.is_empty() {
|
||||
config.gitea_url = Some(val);
|
||||
}
|
||||
}
|
||||
if let Ok(val) = std::env::var("GITEA_TOKEN") {
|
||||
let val = sanitize_string(&val);
|
||||
if !val.is_empty() {
|
||||
config.gitea_token = Some(val);
|
||||
}
|
||||
}
|
||||
if let Ok(val) = std::env::var("REGISTRY_OWNER") {
|
||||
let val = sanitize_string(&val);
|
||||
if !val.is_empty() {
|
||||
config.registry_owner = Some(val);
|
||||
}
|
||||
}
|
||||
if let Ok(val) = std::env::var("GITHUB_TOKEN") {
|
||||
let val = sanitize_string(&val);
|
||||
if !val.is_empty() {
|
||||
config.github_token = Some(val);
|
||||
}
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
/// Speichert die Anwendungskonfiguration auf der Festplatte.
|
||||
|
||||
+9
-8
@@ -1,4 +1,4 @@
|
||||
use anyhow::{bail, Context, Result};
|
||||
use anyhow::{Context, Result, bail};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Unterstützte Linux-Paketverteilungstypen.
|
||||
@@ -87,6 +87,7 @@ pub struct GitHubClient {
|
||||
impl GitHubClient {
|
||||
/// Erstellt einen neuen GitHub-API-Client mit optionalem Authentifizierungstoken.
|
||||
pub fn new(token: Option<String>) -> Result<Self> {
|
||||
let token = token.filter(|t| !t.trim().is_empty());
|
||||
let mut headers = reqwest::header::HeaderMap::new();
|
||||
headers.insert(
|
||||
reqwest::header::USER_AGENT,
|
||||
@@ -105,6 +106,12 @@ impl GitHubClient {
|
||||
Ok(Self { client, token })
|
||||
}
|
||||
|
||||
/// Gibt das optionale Authentifizierungstoken zurück (falls gesetzt).
|
||||
pub fn token(&self) -> Option<&str> {
|
||||
self.token.as_deref()
|
||||
}
|
||||
|
||||
|
||||
/// Ruft Releases für ein angegebenes Repository ab (Format "owner/repo").
|
||||
///
|
||||
/// Wenn `history` false ist, werden nur die neuesten Releases abgerufen.
|
||||
@@ -239,13 +246,7 @@ impl GitHubClient {
|
||||
|
||||
/// Hilfsfunktion zum Parsen von "owner/repo" aus einer Repository-Zeichenkette.
|
||||
pub fn parse_repo_owner_name(repo: &str) -> Result<(&str, &str)> {
|
||||
let cleaned = repo
|
||||
.trim()
|
||||
.trim_start_matches("https://github.com/")
|
||||
.trim_start_matches("http://github.com/")
|
||||
.trim_start_matches("github.com/")
|
||||
.trim_end_matches(".git")
|
||||
.trim_matches('/');
|
||||
let cleaned = crate::utils::clean_repo_input(repo);
|
||||
|
||||
let parts: Vec<&str> = cleaned.split('/').collect();
|
||||
if parts.len() != 2 || parts[0].is_empty() || parts[1].is_empty() {
|
||||
|
||||
@@ -6,3 +6,4 @@ pub mod config;
|
||||
pub mod gitea;
|
||||
pub mod github;
|
||||
pub mod pipeline;
|
||||
pub mod utils;
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
|
||||
/// Sanitizes a string by removing surrounding quotes (single or double).
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `input` - The string to sanitize.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The sanitized string with surrounding quotes removed.
|
||||
pub fn sanitize_string(input: &str) -> String {
|
||||
let trimmed = input.trim();
|
||||
if let Some(stripped) = trimmed.strip_prefix('"').and_then(|s| s.strip_suffix('"')) {
|
||||
stripped.to_string()
|
||||
} else if let Some(stripped) = trimmed.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) {
|
||||
stripped.to_string()
|
||||
} else {
|
||||
trimmed.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// Sanitizes environment variables by removing surrounding quotes.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `env_vars` - A reference to a mutable map of environment variables.
|
||||
pub fn sanitize_env_vars(env_vars: &mut std::collections::HashMap<String, String>) {
|
||||
for (_key, value) in env_vars.iter_mut() {
|
||||
*value = sanitize_string(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Bereinigt eine Repository-Eingabe (URL oder Kurzform), indem Query-Parameter, Fragmente,
|
||||
/// URL-Schemata/Hosts (`github.com`, `www.github.com`, `git@github.com:`), `.git`-Endungen
|
||||
/// und umgebende Schrägstriche entfernt werden.
|
||||
pub fn clean_repo_input(input: &str) -> &str {
|
||||
let mut trimmed = input.trim();
|
||||
if (trimmed.starts_with('"') && trimmed.ends_with('"'))
|
||||
|| (trimmed.starts_with('\'') && trimmed.ends_with('\''))
|
||||
{
|
||||
if trimmed.len() >= 2 {
|
||||
trimmed = trimmed[1..trimmed.len() - 1].trim();
|
||||
}
|
||||
}
|
||||
|
||||
let without_query_or_fragment = match trimmed.find(|c| c == '?' || c == '#') {
|
||||
Some(idx) => &trimmed[..idx],
|
||||
None => trimmed,
|
||||
};
|
||||
|
||||
let mut cleaned = without_query_or_fragment
|
||||
.trim_start_matches("git@github.com:")
|
||||
.trim_start_matches("https://")
|
||||
.trim_start_matches("http://")
|
||||
.trim_start_matches("www.github.com/")
|
||||
.trim_start_matches("github.com/")
|
||||
.trim_matches('/');
|
||||
|
||||
if let Some(stripped) = cleaned.strip_suffix(".git") {
|
||||
cleaned = stripped.trim_matches('/');
|
||||
}
|
||||
|
||||
cleaned
|
||||
}
|
||||
+148
-38
@@ -1,53 +1,163 @@
|
||||
use mirror_package::config::{AppConfig, RepoConfig};
|
||||
use mirror_package::config::{AppConfig, RepoConfig, load_config};
|
||||
use mirror_package::utils::{clean_repo_input, sanitize_env_vars, sanitize_string};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_env_vars() {
|
||||
let mut env_vars = HashMap::new();
|
||||
env_vars.insert("GITEA_URL".to_string(), "\"https://gitea.example.com\"".to_string());
|
||||
env_vars.insert("GITEA_TOKEN".to_string(), "\"token123\"".to_string());
|
||||
env_vars.insert("REGISTRY_OWNER".to_string(), "\"owner\"".to_string());
|
||||
env_vars.insert("GITHUB_TOKEN".to_string(), "\"github_token123\"".to_string());
|
||||
|
||||
sanitize_env_vars(&mut env_vars);
|
||||
|
||||
assert_eq!(env_vars.get("GITEA_URL").unwrap(), "https://gitea.example.com");
|
||||
assert_eq!(env_vars.get("GITEA_TOKEN").unwrap(), "token123");
|
||||
assert_eq!(env_vars.get("REGISTRY_OWNER").unwrap(), "owner");
|
||||
assert_eq!(env_vars.get("GITHUB_TOKEN").unwrap(), "github_token123");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_config_with_env_vars() {
|
||||
// This test relies on environment variables being set externally.
|
||||
// To avoid `unsafe` blocks, we skip setting them programmatically.
|
||||
// In a real test environment, set these variables before running the test:
|
||||
// GITEA_URL="https://gitea.example.com"
|
||||
// GITEA_TOKEN="token123"
|
||||
// REGISTRY_OWNER="owner"
|
||||
// GITHUB_TOKEN="github_token123"
|
||||
let config = load_config();
|
||||
|
||||
// Only assert if the environment variables are set
|
||||
if std::env::var("GITEA_URL").is_ok() {
|
||||
assert_eq!(config.gitea_url, Some("https://gitea.example.com".to_string()));
|
||||
assert_eq!(config.gitea_token, Some("token123".to_string()));
|
||||
assert_eq!(config.registry_owner, Some("owner".to_string()));
|
||||
assert_eq!(config.github_token, Some("github_token123".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_string() {
|
||||
assert_eq!(sanitize_string("\"https://gitea.example.com\""), "https://gitea.example.com");
|
||||
assert_eq!(sanitize_string("'https://gitea.example.com'"), "https://gitea.example.com");
|
||||
assert_eq!(sanitize_string("\"token123\""), "token123");
|
||||
assert_eq!(sanitize_string("'token123'"), "token123");
|
||||
assert_eq!(sanitize_string("\"owner\""), "owner");
|
||||
assert_eq!(sanitize_string("'owner'"), "owner");
|
||||
assert_eq!(sanitize_string("\"github_token123\""), "github_token123");
|
||||
assert_eq!(sanitize_string("'github_token123'"), "github_token123");
|
||||
assert_eq!(sanitize_string("no_quotes"), "no_quotes");
|
||||
assert_eq!(sanitize_string("\"single_quote\""), "single_quote");
|
||||
assert_eq!(sanitize_string("'single_quote'"), "single_quote");
|
||||
assert_eq!(sanitize_string("\"escaped_quote\""), "escaped_quote");
|
||||
// Edge cases: single character inputs
|
||||
assert_eq!(sanitize_string("\""), "\"");
|
||||
assert_eq!(sanitize_string("'"), "'");
|
||||
assert_eq!(sanitize_string("a"), "a");
|
||||
// Edge cases: empty quotes and empty strings
|
||||
assert_eq!(sanitize_string("\"\""), "");
|
||||
assert_eq!(sanitize_string("''"), "");
|
||||
assert_eq!(sanitize_string(""), "");
|
||||
assert_eq!(sanitize_string(" "), "");
|
||||
assert_eq!(sanitize_string(" 'hello' "), "hello");
|
||||
assert_eq!(sanitize_string(" \"world\" "), "world");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clean_repo_input() {
|
||||
assert_eq!(clean_repo_input("owner/repo"), "owner/repo");
|
||||
assert_eq!(clean_repo_input("https://github.com/owner/repo"), "owner/repo");
|
||||
assert_eq!(clean_repo_input("http://github.com/owner/repo"), "owner/repo");
|
||||
assert_eq!(clean_repo_input("github.com/owner/repo"), "owner/repo");
|
||||
assert_eq!(clean_repo_input("https://github.com/owner/repo.git"), "owner/repo");
|
||||
assert_eq!(clean_repo_input("https://github.com/owner/repo/"), "owner/repo");
|
||||
assert_eq!(
|
||||
clean_repo_input("https://github.com/raspberrypi/rpi-imager?tab=readme-ov-file"),
|
||||
"raspberrypi/rpi-imager"
|
||||
);
|
||||
assert_eq!(
|
||||
clean_repo_input("https://github.com/raspberrypi/rpi-imager/?tab=readme-ov-file"),
|
||||
"raspberrypi/rpi-imager"
|
||||
);
|
||||
assert_eq!(
|
||||
clean_repo_input("https://github.com/raspberrypi/rpi-imager.git?tab=readme-ov-file"),
|
||||
"raspberrypi/rpi-imager"
|
||||
);
|
||||
assert_eq!(
|
||||
clean_repo_input("https://github.com/raspberrypi/rpi-imager#readme"),
|
||||
"raspberrypi/rpi-imager"
|
||||
);
|
||||
assert_eq!(
|
||||
clean_repo_input("https://github.com/raspberrypi/rpi-imager?tab=readme-ov-file#install"),
|
||||
"raspberrypi/rpi-imager"
|
||||
);
|
||||
assert_eq!(
|
||||
clean_repo_input("raspberrypi/rpi-imager?tab=readme-ov-file"),
|
||||
"raspberrypi/rpi-imager"
|
||||
);
|
||||
assert_eq!(
|
||||
clean_repo_input("\"https://github.com/raspberrypi/rpi-imager?tab=readme-ov-file\""),
|
||||
"raspberrypi/rpi-imager"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize_repo_name() {
|
||||
let input = "https://github.com/owner/repo.git";
|
||||
let normalized = AppConfig::normalize_repo_name(input);
|
||||
assert_eq!(normalized, "owner/repo");
|
||||
|
||||
let input_with_query = "https://github.com/raspberrypi/rpi-imager?tab=readme-ov-file";
|
||||
assert_eq!(
|
||||
AppConfig::normalize_repo_name("raspberrypi/rpi-imager"),
|
||||
AppConfig::normalize_repo_name(input_with_query),
|
||||
"raspberrypi/rpi-imager"
|
||||
);
|
||||
assert_eq!(
|
||||
AppConfig::normalize_repo_name("https://github.com/raspberrypi/rpi-imager"),
|
||||
"raspberrypi/rpi-imager"
|
||||
);
|
||||
assert_eq!(
|
||||
AppConfig::normalize_repo_name("https://github.com/raspberrypi/rpi-imager.git"),
|
||||
"raspberrypi/rpi-imager"
|
||||
);
|
||||
assert_eq!(
|
||||
AppConfig::normalize_repo_name("Heroic-Games-Launcher/HeroicGamesLauncher/"),
|
||||
"Heroic-Games-Launcher/HeroicGamesLauncher"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_repo_management() {
|
||||
fn test_add_or_update_repo() {
|
||||
let mut config = AppConfig::default();
|
||||
config.add_or_update_repo(RepoConfig::new("owner/repo1", true));
|
||||
config.add_or_update_repo(RepoConfig::new("owner/repo2", false));
|
||||
|
||||
assert_eq!(config.repositories.len(), 2);
|
||||
assert!(config.find_repo("owner/repo1").is_some());
|
||||
assert!(config.find_repo("https://github.com/owner/repo1").is_some());
|
||||
|
||||
assert!(config.remove_repo("owner/repo1"));
|
||||
let repo = RepoConfig::new("https://github.com/owner/repo?tab=readme-ov-file", true);
|
||||
config.add_or_update_repo(repo.clone());
|
||||
assert_eq!(config.repositories.len(), 1);
|
||||
assert!(config.find_repo("owner/repo1").is_none());
|
||||
assert_eq!(config.repositories[0].name, "owner/repo");
|
||||
assert_eq!(config.repositories[0].include_prereleases, true);
|
||||
|
||||
// Updating existing repo with a URL with query param
|
||||
let repo_updated = RepoConfig::new("https://github.com/owner/repo#readme", false);
|
||||
config.add_or_update_repo(repo_updated);
|
||||
assert_eq!(config.repositories.len(), 1);
|
||||
assert_eq!(config.repositories[0].name, "owner/repo");
|
||||
assert_eq!(config.repositories[0].include_prereleases, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialization_roundtrip() {
|
||||
let mut config = AppConfig {
|
||||
gitea_url: Some("https://gitea.example.com".to_string()),
|
||||
gitea_token: Some("secret_token".to_string()),
|
||||
registry_owner: Some("my-org".to_string()),
|
||||
github_token: Some("gh_pat".to_string()),
|
||||
repositories: Vec::new(),
|
||||
};
|
||||
config.add_or_update_repo(RepoConfig::new("raspberrypi/rpi-imager", true));
|
||||
|
||||
let json_str = serde_json::to_string(&config).unwrap();
|
||||
let deserialized: AppConfig = serde_json::from_str(&json_str).unwrap();
|
||||
|
||||
assert_eq!(config, deserialized);
|
||||
fn test_remove_repo() {
|
||||
let mut config = AppConfig::default();
|
||||
let repo = RepoConfig::new("owner/repo", true);
|
||||
config.add_or_update_repo(repo.clone());
|
||||
assert!(config.remove_repo("https://github.com/owner/repo?tab=readme-ov-file"));
|
||||
assert!(config.repositories.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_repo() {
|
||||
let mut config = AppConfig::default();
|
||||
let repo = RepoConfig::new("owner/repo", true);
|
||||
config.add_or_update_repo(repo.clone());
|
||||
assert_eq!(
|
||||
config.find_repo("https://github.com/owner/repo?tab=readme-ov-file"),
|
||||
Some(&RepoConfig::new("owner/repo", true))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_last_synced_tag() {
|
||||
let mut config = AppConfig::default();
|
||||
let repo = RepoConfig::new("owner/repo", true);
|
||||
config.add_or_update_repo(repo.clone());
|
||||
config.update_last_synced_tag("owner/repo", "v1.0.0".to_string());
|
||||
assert_eq!(config.find_repo("owner/repo").unwrap().last_synced_tag, Some("v1.0.0".to_string()));
|
||||
}
|
||||
+33
-1
@@ -1,4 +1,4 @@
|
||||
use mirror_package::github::{parse_repo_owner_name, PackageType};
|
||||
use mirror_package::github::{PackageType, parse_repo_owner_name};
|
||||
|
||||
#[test]
|
||||
fn test_package_classification() {
|
||||
@@ -64,7 +64,39 @@ fn test_parse_repo_owner_name() {
|
||||
parse_repo_owner_name("https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher.git").unwrap(),
|
||||
("Heroic-Games-Launcher", "HeroicGamesLauncher")
|
||||
);
|
||||
assert_eq!(
|
||||
parse_repo_owner_name("https://github.com/raspberrypi/rpi-imager?tab=readme-ov-file").unwrap(),
|
||||
("raspberrypi", "rpi-imager")
|
||||
);
|
||||
assert_eq!(
|
||||
parse_repo_owner_name("https://github.com/raspberrypi/rpi-imager/?tab=readme-ov-file#install").unwrap(),
|
||||
("raspberrypi", "rpi-imager")
|
||||
);
|
||||
assert_eq!(
|
||||
parse_repo_owner_name("raspberrypi/rpi-imager?tab=readme-ov-file").unwrap(),
|
||||
("raspberrypi", "rpi-imager")
|
||||
);
|
||||
|
||||
assert!(parse_repo_owner_name("invalid_repo").is_err());
|
||||
assert!(parse_repo_owner_name("invalid/repo/extra").is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_github_client_empty_token() {
|
||||
use mirror_package::github::GitHubClient;
|
||||
|
||||
// Test that a client with an empty or whitespace token sanitizes it to None
|
||||
let client = GitHubClient::new(Some("".to_string())).expect("Failed to create client with empty token");
|
||||
assert_eq!(client.token(), None);
|
||||
|
||||
let client_whitespace = GitHubClient::new(Some(" ".to_string())).expect("Failed to create client with whitespace token");
|
||||
assert_eq!(client_whitespace.token(), None);
|
||||
|
||||
// Test with a valid token
|
||||
let client_with_token = GitHubClient::new(Some("valid_token".to_string())).expect("Failed to create client with valid token");
|
||||
assert_eq!(client_with_token.token(), Some("valid_token"));
|
||||
|
||||
// Test with no token
|
||||
let client_no_token = GitHubClient::new(None).expect("Failed to create client with no token");
|
||||
assert_eq!(client_no_token.token(), None);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user