├── License ├── README.md ├── Readme-Assignment13.md ├── WorkFlow └── ci.yml ├── branch_protection ├── protection.md └── screenshot.md ├── contributing.md ├── docs └── openapi.json ├── roadmap.md ├── src ├── api │ ├── activity.rs │ ├── error.rs │ ├── mod.rs │ └── users.rs ├── error.rs ├── main.rs └── services │ ├── activity_service.rs │ └── user_service.rs ├── tests ├── api │ └── users.rs └── services │ └── user_service_tests.rs └── updatedReadme.md /License: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 [Your Name or Team] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | HealthTrackerSystem_Assignment12 2 | 3 | Fitness Tracker API 4 | Assignment 12: Service Layer & REST API Implementation** 5 | ![Rust](https://img.shields.io/badge/Rust-1.70%2B-orange) 6 | ![Axum](https://img.shields.io/badge/Axum-0.7-blue) 7 | ![OpenAPI](https://img.shields.io/badge/OpenAPI-3.0-green) 8 | 9 | 🚀 Quick Start 10 | ```bash 11 | Clone and run 12 | git clone https://github.com/your-username/fitness-tracker-rust.git 13 | cd fitness-tracker-rust 14 | cargo run 15 | 16 | Test endpoints 17 | curl -X POST http://localhost:3000/api/users \ 18 | -H "Content-Type: application/json" \ 19 | -d '{"email":"test@fit.com","name":"Alice"}' 20 | 21 | 22 | Project Structure 23 | 24 | . 25 | ├── src/ 26 | │ ├── services/ # Business logic (User/Activity services) 27 | │ ├── api/ # REST controllers 28 | │ ├── repositories/ # Data persistence (A11) 29 | ├── tests/ 30 | │ ├── services/ # Unit tests 31 | │ └── api/ # Integration tests 32 | ├── docs/ # OpenAPI specs 33 | └── Cargo.toml 34 | 35 | 36 | API Endpoints 37 | 38 | Users 39 | Method Endpoint Description 40 | POST /api/users Register new user 41 | GET /api/users/{id} Get user profile 42 | Activities 43 | Method Endpoint Description 44 | POST /api/activities Start new activity 45 | POST /api/activities/{id}/complete Complete activity 46 | 47 | Example Request: 48 | 49 | curl -X POST http://localhost:3000/api/activities \ 50 | -H "Content-Type: application/json" \ 51 | -d '{"user_id":"user123","activity_type":"Running"} 52 | 53 | 54 | Testing 55 | 56 | # Run unit tests (services) 57 | cargo test --test services 58 | 59 | # Run integration tests (API) 60 | cargo test --test api 61 | 62 | # Test coverage (requires grcov) 63 | cargo tarpaulin --ignore-tests 64 | 65 | Documentation 66 | 67 | Interactive API docs available at: 68 | http://localhost:3000/docs 69 | 70 | Swagger UI 71 | 72 | 🛠️ Development 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /Readme-Assignment13.md: -------------------------------------------------------------------------------- 1 | # 🏃 Health Fitness Tracker – Assignment 13: CI/CD Pipeline 2 | 3 | This README outlines how continuous integration and delivery (CI/CD) are implemented for the Health Fitness Tracker system using GitHub Actions. 4 | 5 | --- 6 | 7 | ## 🧪 Run Tests Locally 8 | 9 | To verify the system locally: 10 | 11 | ```bash 12 | cargo build 13 | cargo test 14 | ``` 15 | 16 | --- 17 | 18 | ## 🚀 CI/CD Pipeline 19 | 20 | Our `.github/workflows/ci.yml` file automates testing and artifact delivery: 21 | 22 | ### ✅ Continuous Integration (CI) 23 | - **Trigger**: On push and PR to `main` 24 | - **Steps**: 25 | - Checkout code 26 | - Set up stable Rust 27 | - Build project 28 | - Run all unit/integration tests 29 | 30 | ### 📦 Continuous Delivery (CD) 31 | - **Trigger**: Push directly to `main` 32 | - **Steps**: 33 | - Archive code as `health-fitness-tracker.zip` 34 | - Upload as GitHub Action artifact 35 | 36 | --- 37 | 38 | ## 🔐 Branch Protection 39 | 40 | Enabled on the `main` branch to enforce code quality: 41 | 42 | - ✅ Require pull request reviews 43 | - ✅ Require status checks to pass 44 | - ✅ Block direct pushes to `main` 45 | 46 | See [`PROTECTION.md`](./PROTECTION.md) for details. 47 | 48 | --- 49 | 50 | ## 📁 Key Files 51 | 52 | ``` 53 | .github/workflows/ci.yml # CI/CD automation 54 | PROTECTION.md # Branch protection policy 55 | ``` 56 | 57 | --- 58 | 59 | ## 📸 Required Screenshots (not included in repo) 60 | 61 | - Branch protection rules UI 62 | - Green checkmarks on CI job 63 | - PR blocked by test failure 64 | - Artifact uploaded to Actions tab 65 | 66 | --- 67 | 68 | 📅 **Submitted:** May 2025 69 | -------------------------------------------------------------------------------- /WorkFlow/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI Pipeline 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build-and-test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v3 15 | 16 | - name: Set up Rust 17 | uses: actions/setup-rust@v1 18 | with: 19 | rust-version: stable 20 | 21 | - name: Build Health Tracker 22 | run: cargo build --verbose 23 | 24 | - name: Run Health Tracker Tests 25 | run: cargo test --all --verbose 26 | 27 | release-artifact: 28 | if: github.ref == 'refs/heads/main' && github.event_name == 'push' 29 | runs-on: ubuntu-latest 30 | needs: build-and-test 31 | steps: 32 | - name: Checkout code 33 | uses: actions/checkout@v3 34 | 35 | - name: Create artifact ZIP 36 | run: zip -r health-fitness-tracker.zip . 37 | 38 | - name: Upload artifact 39 | uses: actions/upload-artifact@v3 40 | with: 41 | name: health-fitness-tracker-artifact 42 | path: health-fitness-tracker.zip 43 | 44 | -------------------------------------------------------------------------------- /branch_protection/protection.md: -------------------------------------------------------------------------------- 1 | 🔐 Branch Protection Rules – PROTECTION.md 2 | 3 | To maintain quality and prevent regressions in the Health Fitness Tracker System, we enforce branch protection rules on the `main` branch. 4 | 5 | ✅ Enforced Rules 6 | 7 | | Rule | Description | 8 | |------------------------------|-------------| 9 | | ✅ Require pull request reviews | All changes to `main` must be reviewed by at least one other contributor. | 10 | | ✅ Require status checks to pass | GitHub Actions must verify the code builds and passes all tests. | 11 | | ✅ Block direct pushes | No developer can push directly to `main`—all changes go through PRs. | 12 | 13 | 🔍 Why It Matters 14 | 15 | - **Code Integrity**: Prevents unreviewed or broken code from reaching production. 16 | - **Collaboration**: Encourages team peer review and shared ownership. 17 | - **Quality Assurance**: CI runs catch issues before merge. 18 | - **Compliance**: Aligns with modern DevOps workflows and version control best practices. 19 | 20 | ⚙️ How to Configure 21 | 22 | On GitHub: 23 | - Go to `Settings → Branches → Add Rule → main` 24 | - Check: 25 | - ✅ Require pull request reviews 26 | - ✅ Require status checks to pass 27 | - ✅ Include administrators (optional) 28 | 29 | --- 30 | 31 | > These rules help ensure that only tested and reviewed code enters the main release branch, supporting a reliable health data platform. 32 | 33 | 34 | -------------------------------------------------------------------------------- /branch_protection/screenshot.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # 🏃 Contributing to the Health Fitness Tracker System 2 | 3 | Thank you for your interest in contributing to the Health Fitness Tracker System! We welcome community involvement. 4 | 5 | --- 6 | 7 | ## ✅ Getting Started 8 | 9 | 1. **Fork this repository** on GitHub. 10 | 2. **Clone your fork**: 11 | ```bash 12 | git clone https://github.com/your-username/health-fitness-tracker.git 13 | ``` 14 | 3. **Install dependencies**: 15 | ```bash 16 | cargo build 17 | ``` 18 | 4. **Run all tests**: 19 | ```bash 20 | cargo test 21 | ``` 22 | 23 | --- 24 | 25 | ## 📐 Coding Standards 26 | 27 | - Format your code using `cargo fmt` 28 | - Check for warnings using `cargo clippy` 29 | - Follow semantic commit conventions: 30 | - `feat: add new step counter feature` 31 | - `fix: correct BMI calculation` 32 | 33 | --- 34 | 35 | ## 🔁 Pull Request Process 36 | 37 | 1. Work in a feature branch: `git checkout -b feat/your-feature` 38 | 2. Submit a PR with a clear title and summary. 39 | 3. Ensure tests pass and CI checks are green. 40 | 4. At least one review is required before merging. 41 | 42 | --- 43 | 44 | ## 🏷️ Issue Labels 45 | 46 | We use the following labels to help contributors find the right tasks: 47 | 48 | - `good-first-issue` – great for newcomers 49 | - `feature-request` – ideas for new functionality 50 | 51 | --- 52 | 53 | Thank you for helping us build a better fitness tracker! 💪 54 | -------------------------------------------------------------------------------- /docs/openapi.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.3", 3 | "info": { 4 | "title": "Fitness Tracker API", 5 | "version": "1.0" 6 | }, 7 | "paths": { 8 | "/api/users": { 9 | "post": { 10 | "tags": ["Users"], 11 | "responses": { 12 | "201": { 13 | "description": "User created successfully", 14 | "content": { 15 | "application/json": { 16 | "schema": { 17 | "$ref": "#/components/schemas/User" 18 | } 19 | } 20 | } 21 | }, 22 | "400": { 23 | "description": "Validation error", 24 | "content": { 25 | "application/json": { 26 | "example": { 27 | "error": "Invalid email format" 28 | } 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /roadmap.md: -------------------------------------------------------------------------------- 1 | # 🌦️ Assignment 13 – CI/CD Pipeline for Weather Tracking System 2 | 3 | This README outlines the CI/CD automation and protection rules implemented for the Weather Tracking System project using **GitHub Actions**. 4 | 5 | --- 6 | 7 | ## ✅ Local Development 8 | 9 | To build and test the project locally: 10 | 11 | ```bash 12 | cargo build 13 | cargo test 14 | ``` 15 | 16 | --- 17 | 18 | ## 🚀 CI/CD Pipeline Overview 19 | 20 | The CI/CD pipeline is configured in `.github/workflows/ci.yml`. It performs the following: 21 | 22 | ### 🔁 Continuous Integration (CI) 23 | - **Trigger**: On `push` and `pull_request` to `main` 24 | - **Steps**: 25 | - Checkout code 26 | - Set up stable Rust toolchain 27 | - Build the project 28 | - Run unit & integration tests 29 | 30 | ### 📦 Continuous Delivery (CD) 31 | - **Trigger**: On direct `push` to `main` 32 | - **Steps**: 33 | - Archive the entire Weather Tracking System as `weather-tracking-system.zip` 34 | - Upload the archive as a GitHub Actions artifact 35 | - (Optional) Can be extended to publish to a registry or deploy via Docker in future 36 | 37 | Example snippet from `ci.yml`: 38 | 39 | ```yaml 40 | release-artifact: 41 | if: github.ref == 'refs/heads/main' && github.event_name == 'push' 42 | runs-on: ubuntu-latest 43 | needs: build-and-test 44 | steps: 45 | - uses: actions/checkout@v3 46 | - run: zip -r weather-tracking-system.zip . 47 | - uses: actions/upload-artifact@v3 48 | with: 49 | name: weather-artifact 50 | path: weather-tracking-system.zip 51 | ``` 52 | 53 | --- 54 | 55 | ## 🔐 Branch Protection 56 | 57 | To ensure high code quality and production stability, the following rules are enforced on the `main` branch: 58 | 59 | - ✅ Require pull request reviews (at least 1) 60 | - ✅ Require status checks to pass (CI must succeed) 61 | - ✅ Block direct pushes 62 | 63 | More details in [`PROTECTION.md`](./PROTECTION.md) 64 | 65 | --- 66 | 67 | ## 🤝 Getting Started for Contributors 68 | 69 | ```bash 70 | git clone https://github.com/your-username/health-fitness-tracker.git 71 | cd health-fitness-tracker 72 | cargo build && cargo test 73 | ``` 74 | 75 | See [`CONTRIBUTING.md`](./CONTRIBUTING.md) for contribution instructions. 76 | 77 | --- 78 | 79 | ## 🔮 Roadmap Preview 80 | 81 | Check out the [`ROADMAP.md`](./ROADMAP.md) for upcoming features, DevOps improvements, and community collaboration opportunities. 82 | 83 | --- 84 | 85 | ## 🌟 Contribution Highlights 86 | 87 | | Label | Description | 88 | |-------------------|------------------------------------| 89 | | good-first-issue | Ideal for beginners | 90 | | feature-request | Suggested enhancements or modules | 91 | 92 | --- 93 | 94 | ## 📁 Key Files 95 | 96 | ``` 97 | .github/workflows/ci.yml # CI/CD workflow 98 | PROTECTION.md # Branch protection policy 99 | CONTRIBUTING.md # How to contribute 100 | ROADMAP.md # Upcoming features 101 | LICENSE # Open-source license 102 | openapi.yaml # API docs 103 | ``` 104 | 105 | --- 106 | 107 | ## ✅ Example Screenshots (not included in markdown) 108 | 109 | - Passing test badge 110 | - Branch protection rule screen 111 | - Pull request blocked by failed test 112 | - Release artifact generated after successful merge 113 | 114 | --- 115 | 116 | 📅 **Submitted:** May 2025 117 | -------------------------------------------------------------------------------- /src/api/activity.rs: -------------------------------------------------------------------------------- 1 | 2 | use axum::{ 3 | extract::{Path, State, Json}, 4 | routing::{post, delete}, 5 | Router 6 | }; 7 | use crate::{services::ActivityService, models::{Activity, ActivityType}}; 8 | 9 | pub fn routes(activity_service: ActivityService) -> Router { 10 | Router::new() 11 | .route("/", post(start_activity)) 12 | .route("/:id/complete", post(complete_activity)) 13 | .with_state(activity_service) 14 | } 15 | 16 | // POST /api/activities 17 | async fn start_activity( 18 | State(service): State>, 19 | Json(payload): Json, 20 | ) -> Result { 21 | let activity = service 22 | .start_activity(payload.user_id, payload.activity_type) 23 | .await?; 24 | Ok((StatusCode::CREATED, Json(activity))) 25 | } 26 | 27 | // POST /api/activities/:id/complete 28 | async fn complete_activity( 29 | Path(activity_id): Path, 30 | State(service): State>, 31 | ) -> Result, ApiError> { 32 | let activity = service.complete_activity(activity_id).await?; 33 | Ok(Json(activity)) 34 | } 35 | 36 | #[derive(serde::Deserialize)] 37 | struct StartActivityRequest { 38 | user_id: String, 39 | activity_type: ActivityType, 40 | } 41 | -------------------------------------------------------------------------------- /src/api/error.rs: -------------------------------------------------------------------------------- 1 | 2 | // src/api/error.rs 3 | #[derive(utoipa::ToSchema)] 4 | pub enum ApiError { 5 | #[schema(example = "Resource not found")] 6 | NotFound, 7 | #[schema(example = "Invalid email format")] 8 | Validation(String), 9 | #[schema(example = "Maximum 3 concurrent activities allowed")] 10 | BusinessRuleViolation(String), 11 | } 12 | -------------------------------------------------------------------------------- /src/api/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | // src/api/mod.rs 3 | use utoipa::OpenApi; 4 | 5 | #[derive(OpenApi)] 6 | #[openapi( 7 | paths( 8 | users::create_user, 9 | users::get_user, 10 | activities::start_activity 11 | ), 12 | components( 13 | schemas(User, CreateUserRequest, Activity, ApiError) 14 | ), 15 | tags( 16 | (name = "Users", description = "User management"), 17 | (name = "Activities", description = "Fitness activities") 18 | ) 19 | )] 20 | pub struct ApiDoc; 21 | 22 | pub fn routes(/* ... */) -> Router { 23 | let swagger = SwaggerUi::new("/docs") 24 | .url("/api-docs/openapi.json", ApiDoc::openapi()); 25 | 26 | Router::new() 27 | .merge(swagger) 28 | // ... existing routes 29 | } 30 | -------------------------------------------------------------------------------- /src/api/users.rs: -------------------------------------------------------------------------------- 1 | 2 | // src/api/users.rs 3 | use utoipa::ToSchema; 4 | 5 | #[derive(serde::Deserialize, ToSchema)] 6 | pub struct CreateUserRequest { 7 | /// User's email address 8 | #[schema(example = "user@example.com")] 9 | pub email: String, 10 | 11 | /// User's full name 12 | #[schema(example = "John Doe")] 13 | pub name: String, 14 | } 15 | 16 | #[utoipa::path( 17 | post, 18 | path = "/api/users", 19 | request_body = CreateUserRequest, 20 | responses( 21 | (status = 201, description = "User created successfully", body = User), 22 | (status = 400, description = "Validation error", 23 | body = ApiError, 24 | example = json!({"error": "Invalid email format"})), 25 | (status = 500, description = "Internal server error") 26 | ), 27 | tag = "Users" 28 | )] 29 | async fn create_user(/* ... */) { /* ... */ } 30 | 31 | #[utoipa::path( 32 | get, 33 | path = "/api/users/{id}", 34 | params( 35 | ("id" = String, Path, description = "User ID") 36 | ), 37 | responses( 38 | (status = 200, description = "User details", body = User), 39 | (status = 404, description = "User not found", 40 | body = ApiError, 41 | example = json!({"error": "User not found"})) 42 | ), 43 | tag = "Users" 44 | )] 45 | async fn get_user(/* ... */) { /* ... */ } 46 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | 2 | #[derive(Debug)] 3 | pub enum ServiceError { 4 | Validation(String), 5 | BusinessRuleViolation(String), 6 | NotFound, 7 | RepositoryError(Box), 8 | } 9 | 10 | impl From> for ServiceError { 11 | fn from(err: Box) -> Self { 12 | ServiceError::RepositoryError(err) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | 2 | use axum::Router; 3 | use crate::{api::routes, repositories::{InMemoryUserRepository, InMemoryActivityRepository}}; 4 | 5 | #[tokio::main] 6 | async fn main() { 7 | let user_repo = InMemoryUserRepository::new(); 8 | let activity_repo = InMemoryActivityRepository::new(); 9 | 10 | let app = routes( 11 | UserService::new(user_repo), 12 | ActivityService::new(activity_repo) 13 | ); 14 | 15 | axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) 16 | .serve(app.into_make_service()) 17 | .await 18 | .unwrap(); 19 | } 20 | -------------------------------------------------------------------------------- /src/services/activity_service.rs: -------------------------------------------------------------------------------- 1 | 2 | use crate::{ 3 | models::{Activity, ActivityType}, 4 | repositories::ActivityRepository, 5 | error::ServiceError, 6 | }; 7 | 8 | pub struct ActivityService { 9 | repo: R, 10 | max_concurrent_activities: usize, 11 | } 12 | 13 | impl ActivityService { 14 | pub fn new(repo: R) -> Self { 15 | Self { 16 | repo, 17 | max_concurrent_activities: 3, // Business rule 18 | } 19 | } 20 | 21 | /// Start new activity with validation 22 | pub async fn start_activity( 23 | &mut self, 24 | user_id: String, 25 | activity_type: ActivityType, 26 | ) -> Result { 27 | // Check existing active activities 28 | let active_activities = self.repo 29 | .find_by_user(user_id.clone()) 30 | .await? 31 | .into_iter() 32 | .filter(|a| a.end_time.is_none()) 33 | .count(); 34 | 35 | // Business rule enforcement 36 | if active_activities >= self.max_concurrent_activities { 37 | return Err(ServiceError::BusinessRuleViolation( 38 | "Maximum 3 concurrent activities allowed".into(), 39 | )); 40 | } 41 | 42 | let activity = Activity::new(user_id, activity_type); 43 | self.repo.save(activity.clone()).await?; 44 | Ok(activity) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/services/user_service.rs: -------------------------------------------------------------------------------- 1 | 2 | use crate::{ 3 | models::User, 4 | repositories::UserRepository, 5 | error::ServiceError, 6 | }; 7 | 8 | pub struct UserService { 9 | repo: R, 10 | } 11 | 12 | impl UserService { 13 | pub fn new(repo: R) -> Self { 14 | Self { repo } 15 | } 16 | 17 | /// Register user with validation 18 | pub async fn register_user( 19 | &mut self, 20 | email: String, 21 | name: String, 22 | ) -> Result { 23 | // Validate input 24 | if email.is_empty() || !email.contains('@') { 25 | return Err(ServiceError::Validation("Invalid email format".into())); 26 | } 27 | 28 | // Business rule: Name must be at least 2 characters 29 | if name.len() < 2 { 30 | return Err(ServiceError::Validation("Name too short".into())); 31 | } 32 | 33 | let user = User::new(email, name); 34 | self.repo.save(user.clone()).await?; 35 | Ok(user) 36 | } 37 | 38 | /// Get user by ID with error handling 39 | pub async fn get_user(&self, user_id: &str) -> Result { 40 | self.repo 41 | .find_by_id(user_id.to_string()) 42 | .await? 43 | .ok_or(ServiceError::NotFound) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/api/users.rs: -------------------------------------------------------------------------------- 1 | 2 | // tests/api/users.rs 3 | use axum::{ 4 | body::Body, 5 | http::{Request, StatusCode}, 6 | }; 7 | use tower::ServiceExt; 8 | 9 | #[tokio::test] 10 | async fn test_user_creation() { 11 | let repo = InMemoryUserRepository::new(); 12 | let service = UserService::new(repo); 13 | let app = api::routes(service, ActivityService::new(InMemoryActivityRepository::new())); 14 | 15 | let response = app 16 | .oneshot( 17 | Request::builder() 18 | .method("POST") 19 | .uri("/api/users") 20 | .header("Content-Type", "application/json") 21 | .body(Body::from( 22 | r#"{"email": "test@fit.com", "name": "Alice"}"#, 23 | )) 24 | .unwrap(), 25 | ) 26 | .await 27 | .unwrap(); 28 | 29 | assert_eq!(response.status(), StatusCode::CREATED); 30 | } 31 | 32 | #[tokio::test] 33 | async fn test_invalid_user_email() { 34 | let repo = InMemoryUserRepository::new(); 35 | let service = UserService::new(repo); 36 | let app = api::routes(service, ActivityService::new(InMemoryActivityRepository::new())); 37 | 38 | let response = app 39 | .oneshot( 40 | Request::builder() 41 | .method("POST") 42 | .uri("/api/users") 43 | .header("Content-Type", "application/json") 44 | .body(Body::from( 45 | r#"{"email": "bad-email", "name": "Alice"}"#, 46 | )) 47 | .unwrap(), 48 | ) 49 | .await 50 | .unwrap(); 51 | 52 | assert_eq!(response.status(), StatusCode::BAD_REQUEST); 53 | } 54 | -------------------------------------------------------------------------------- /tests/services/user_service_tests.rs: -------------------------------------------------------------------------------- 1 | // tests/services/user_service_test.rs 2 | #[tokio::test] 3 | async fn test_register_user_validation() { 4 | let mut repo = InMemoryUserRepository::new(); 5 | let mut service = UserService::new(repo); 6 | 7 | // Test invalid email 8 | let result = service.register_user("bad-email".into(), "Alice".into()).await; 9 | assert!(matches!(result, Err(ServiceError::Validation(_))); 10 | 11 | // Test valid registration 12 | let user = service.register_user("alice@fit.com".into(), "Alice".into()) 13 | .await 14 | .unwrap(); 15 | assert_eq!(user.email, "alice@fit.com"); 16 | } 17 | 18 | // tests/services/activity_service_test.rs 19 | #[tokio::test] 20 | async fn test_concurrent_activity_limit() { 21 | let mut repo = InMemoryActivityRepository::new(); 22 | let mut service = ActivityService::new(repo); 23 | 24 | let user_id = "user1".to_string(); 25 | 26 | // Create 3 activities (limit) 27 | for _ in 0..3 { 28 | service.start_activity(user_id.clone(), ActivityType::Running).await.unwrap(); 29 | } 30 | 31 | // 4th should fail 32 | let result = service.start_activity(user_id.clone(), ActivityType::Cycling).await; 33 | assert!(matches!(result, Err(ServiceError::BusinessRuleViolation(_))); 34 | } 35 | 36 | -------------------------------------------------------------------------------- /updatedReadme.md: -------------------------------------------------------------------------------- 1 | 🌦️ Assignment 13 – CI/CD Pipeline for Weather Tracking System 2 | 3 | This README outlines the CI/CD automation and protection rules implemented for the Weather Tracking System project using **GitHub Actions**. 4 | 5 | --- 6 | 7 | ✅ Local Development 8 | 9 | To build and test the project locally: 10 | 11 | ```bash 12 | cargo build 13 | cargo test 14 | ``` 15 | 16 | --- 17 | 18 | 🚀 CI/CD Pipeline Overview 19 | 20 | The CI/CD pipeline is configured in `.github/workflows/ci.yml`. It performs the following: 21 | 22 | 🔁 Continuous Integration (CI) 23 | - **Trigger**: On `push` and `pull_request` to `main` 24 | - **Steps**: 25 | - Checkout code 26 | - Set up stable Rust toolchain 27 | - Build the project 28 | - Run unit & integration tests 29 | 30 | ### 📦 Continuous Delivery (CD) 31 | - **Trigger**: On direct `push` to `main` 32 | - **Steps**: 33 | - Archive the entire Weather Tracking System as `weather-tracking-system.zip` 34 | - Upload the archive as a GitHub Actions artifact 35 | - (Optional) Can be extended to publish to a registry or deploy via Docker in future 36 | 37 | Example snippet from `ci.yml`: 38 | 39 | ```yaml 40 | release-artifact: 41 | if: github.ref == 'refs/heads/main' && github.event_name == 'push' 42 | runs-on: ubuntu-latest 43 | needs: build-and-test 44 | steps: 45 | - uses: actions/checkout@v3 46 | - run: zip -r weather-tracking-system.zip . 47 | - uses: actions/upload-artifact@v3 48 | with: 49 | name: weather-artifact 50 | path: weather-tracking-system.zip 51 | ``` 52 | 53 | --- 54 | 55 | 🔐 Branch Protection 56 | 57 | To ensure high code quality and production stability, the following rules are enforced on the `main` branch: 58 | 59 | - ✅ Require pull request reviews (at least 1) 60 | - ✅ Require status checks to pass (CI must succeed) 61 | - ✅ Block direct pushes 62 | 63 | More details in [`PROTECTION.md`](./PROTECTION.md) 64 | 65 | --- 66 | 67 | 🤝 Getting Started for Contributors 68 | 69 | ```bash 70 | git clone https://github.com/your-username/health-fitness-tracker.git 71 | cd health-fitness-tracker 72 | cargo build && cargo test 73 | ``` 74 | 75 | See [`CONTRIBUTING.md`](./CONTRIBUTING.md) for contribution instructions. 76 | 77 | --- 78 | 79 | 🔮 Roadmap Preview 80 | 81 | Check out the [`ROADMAP.md`](./ROADMAP.md) for upcoming features, DevOps improvements, and community collaboration opportunities. 82 | 83 | --- 84 | 85 | 🌟 Contribution Highlights 86 | 87 | | Label | Description | 88 | |-------------------|------------------------------------| 89 | | good-first-issue | Ideal for beginners | 90 | | feature-request | Suggested enhancements or modules | 91 | 92 | --- 93 | 94 | 📁 Key Files 95 | 96 | ``` 97 | .github/workflows/ci.yml # CI/CD workflow 98 | PROTECTION.md # Branch protection policy 99 | CONTRIBUTING.md # How to contribute 100 | ROADMAP.md # Upcoming features 101 | LICENSE # Open-source license 102 | openapi.yaml # API docs 103 | ``` 104 | 105 | --- 106 | 107 | ✅ Example Screenshots (not included in markdown) 108 | 109 | - Passing test badge 110 | - Branch protection rule screen 111 | - Pull request blocked by failed test 112 | - Release artifact generated after successful merge 113 | 114 | --- 115 | 116 | 📅 **Submitted:** May 2025 117 | --------------------------------------------------------------------------------