From 9f02043b795d17772c9cb2f964c98fdf6ae5c765 Mon Sep 17 00:00:00 2001 From: Chris Ackermann Date: Fri, 28 Nov 2025 23:29:31 -0500 Subject: [PATCH 01/13] Experimenting with new actions --- .github/workflows/test.yml | 25 +++++++++++++++++++ .../autotag.yaml | 0 .../autotest.yml | 0 .../triage_issues.yml | 0 4 files changed, 25 insertions(+) create mode 100644 .github/workflows/test.yml rename {.github/workflows => workflows-archive}/autotag.yaml (100%) rename {.github/workflows => workflows-archive}/autotest.yml (100%) rename {.github/workflows => workflows-archive}/triage_issues.yml (100%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..7992fa2 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,25 @@ +name: Python CI Pipeline + +on: + push: + branches: ["development"] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install Dependencies + run: + pip install -r requirements.txt + + - name: Run tests + run: pytest -q \ No newline at end of file diff --git a/.github/workflows/autotag.yaml b/workflows-archive/autotag.yaml similarity index 100% rename from .github/workflows/autotag.yaml rename to workflows-archive/autotag.yaml diff --git a/.github/workflows/autotest.yml b/workflows-archive/autotest.yml similarity index 100% rename from .github/workflows/autotest.yml rename to workflows-archive/autotest.yml diff --git a/.github/workflows/triage_issues.yml b/workflows-archive/triage_issues.yml similarity index 100% rename from .github/workflows/triage_issues.yml rename to workflows-archive/triage_issues.yml From 4ca38560a90aa0ad0c39ef7c86f3dddde05b2d6e Mon Sep 17 00:00:00 2001 From: Chris Ackermann Date: Fri, 28 Nov 2025 23:30:46 -0500 Subject: [PATCH 02/13] Experimenting with new actions --- tests/test_substring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_substring.py b/tests/test_substring.py index 70d2b40..a080210 100644 --- a/tests/test_substring.py +++ b/tests/test_substring.py @@ -15,7 +15,7 @@ def test_equal_substrings(self): def test_partial_substrings(self): score:float = string_utils.calculate_match_degree("software engineering","building engineering") - self.assertEqual(score, 0.9) + self.assertEqual(score, 0.6) if __name__ == "__main__": unittest.main() \ No newline at end of file From a529c6174e1790018e62415ab84531b239839a66 Mon Sep 17 00:00:00 2001 From: Chris Ackermann Date: Fri, 28 Nov 2025 23:55:11 -0500 Subject: [PATCH 03/13] Changed test application and added additional checks --- .github/workflows/test.yml | 9 +++- app/delivery.py | 69 +++++++++++++++++++++++++++ app/string_utils.py | 25 ---------- requirements.txt | 3 +- tests/test_delivery.py | 88 +++++++++++++++++++++++++++++++++++ tests/test_string_distance.py | 23 --------- tests/test_substring.py | 21 --------- 7 files changed, 166 insertions(+), 72 deletions(-) create mode 100644 app/delivery.py delete mode 100644 app/string_utils.py create mode 100644 tests/test_delivery.py delete mode 100644 tests/test_string_distance.py delete mode 100644 tests/test_substring.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7992fa2..e307c66 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,10 +16,17 @@ jobs: uses: actions/setup-python@v5 with: python-version: "3.11" + + - name: Run linter + run: ruff check . + - name: Check formatting + run: black --check . + - name: Install Dependencies run: pip install -r requirements.txt - name: Run tests - run: pytest -q \ No newline at end of file + run: pytest -q + diff --git a/app/delivery.py b/app/delivery.py new file mode 100644 index 0000000..07394dd --- /dev/null +++ b/app/delivery.py @@ -0,0 +1,69 @@ +import math +from typing import Callable + + +class DeliveryMode: + NORMAL = 1 + TURBO = 2 + HYPERJUMP = 3 + + +MODE_SPEED = { + DeliveryMode.NORMAL: 10, # light-minutes per hour + DeliveryMode.TURBO: 25, + DeliveryMode.HYPERJUMP: 100, +} + +# Planet distances from “Galactic Pizza Hub” in light-minutes +PLANET_DISTANCE = { + "Mercury": 3, + "Venus": 2, + "Earth": 0.5, + "Mars": 4, + "Jupiter": 25, + "Saturn": 50, + "Neptune": 200, +} + + +def estimate_delivery_time( + planet: str, + mode: int = DeliveryMode.NORMAL, + surge_load: float = 1.0, + weather_delay_fn: Callable[[], float] = lambda: 0.0, +) -> float: + """ + Estimate total delivery time in hours. + + - `planet`: destination planet name + - `mode`: delivery mode speed multiplier + - `surge_load`: factor (>=1); simulates high-order load + - `weather_delay_fn`: returns number of hours to add (stochastic) + """ + + if planet not in PLANET_DISTANCE: + raise ValueError(f"Unknown destination: {planet}") + + if surge_load < 1: + raise ValueError("surge_load must be >= 1") + + if mode not in MODE_SPEED: + raise ValueError("Invalid delivery mode") + + distance = PLANET_DISTANCE[planet] # light-minutes + + # Base time + speed = MODE_SPEED[mode] + travel_time = distance / speed + + # If distance is extremely large, apply nonlinear fatigue penalty + if distance > 100: + travel_time *= 1.2 # 20% fatigue penalty + + # Weather delay (provided by injected function) + weather_delay = weather_delay_fn() + if weather_delay < 0: + raise ValueError("weather_delay_fn returned negative delay") + + total_time = (travel_time * surge_load) + weather_delay + return round(total_time, 2) diff --git a/app/string_utils.py b/app/string_utils.py deleted file mode 100644 index 3a57a95..0000000 --- a/app/string_utils.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -Utility to perform advanced string comparisons. -""" - -from difflib import SequenceMatcher -import textdistance - - -def calculate_match_degree(txt1:str,txt2:str) -> float: - """ - Given two strings, finds the longest common substring. - Returns the degree of the match based on that longest - substring. - """ - match = SequenceMatcher(None, txt1, txt2).find_longest_match() - return match.size/max(len(txt1),len(txt2)) - -def calcualte_text_distance(txt1:str,txt2:str) -> float: - """ - Uses a text distance metric to calculate the similarity - between two texts. This is not a sub-string match but a - comparison of similar terms occurring in both texts. - """ - algs = textdistance.algorithms - return algs.levenshtein.normalized_similarity(txt1, txt2) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 282786f..f9ddc40 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1 @@ -pytest==8.3.3 -textdistance==4.6.3 \ No newline at end of file +pytest==8.3.3 \ No newline at end of file diff --git a/tests/test_delivery.py b/tests/test_delivery.py new file mode 100644 index 0000000..ca954e0 --- /dev/null +++ b/tests/test_delivery.py @@ -0,0 +1,88 @@ + +# Make sure the test finds the application code +import os +import sys +sys.path.insert(0, os.path.abspath('.')) + +import pytest +from app.delivery import estimate_delivery_time, DeliveryMode + + +def no_weather(): + return 0.0 + + +def fixed_weather(): + return 2.5 + + +# ------------------------------ +# Black-box functional tests +# ------------------------------ + +def test_basic_delivery_normal_mode(): + """Earth is 0.5 lm away; NORMAL speed 10 lm/hr → 0.05 hr.""" + assert estimate_delivery_time("Earth", DeliveryMode.NORMAL, 1.0, no_weather) == 0.05 + + +def test_turbo_delivery(): + """Mars (4 lm) at TURBO speed 25 → 0.16.""" + assert estimate_delivery_time("Mars", DeliveryMode.TURBO, 1.0, no_weather) == 0.16 + + +def test_weather_delay_added(): + """Jupiter (25 lm at 10 lm/hr = 2.5 hr) + 2.5 hr weather.""" + assert estimate_delivery_time("Jupiter", DeliveryMode.NORMAL, 1.0, fixed_weather) == 5.0 + + +# ------------------------------ +# Error-handling tests +# ------------------------------ + +def test_invalid_planet(): + with pytest.raises(ValueError): + estimate_delivery_time("Pluto", DeliveryMode.NORMAL, 1.0, no_weather) + + +def test_invalid_mode(): + with pytest.raises(ValueError): + estimate_delivery_time("Earth", 999, 1.0, no_weather) + + +def test_invalid_surge_load(): + with pytest.raises(ValueError): + estimate_delivery_time("Earth", DeliveryMode.NORMAL, 0.5, no_weather) + + +def test_negative_weather_delay(): + with pytest.raises(ValueError): + estimate_delivery_time("Earth", DeliveryMode.NORMAL, 1.0, lambda: -1) + + +# ------------------------------ +# Boundary value tests +# ------------------------------ + +def test_boundary_no_fatigue_penalty(): + """Distance = 50 → below 100, so no fatigue penalty.""" + assert estimate_delivery_time("Saturn", DeliveryMode.HYPERJUMP, 1.0, no_weather) == 0.5 + + +def test_fatigue_penalty_kicks_in(): + """Neptune = 200 lm → fatigue penalty applied (×1.2).""" + base = 200 / 100 # HYPERJUMP speed + expected = round((base * 1.2), 2) + assert estimate_delivery_time("Neptune", DeliveryMode.HYPERJUMP, 1.0, no_weather) == expected + + +# ------------------------------ +# Category partitioning tests +# ------------------------------ + +@pytest.mark.parametrize("planet", ["Earth", "Mars"]) +@pytest.mark.parametrize("mode", [DeliveryMode.NORMAL, DeliveryMode.TURBO]) +@pytest.mark.parametrize("surge", [1.0, 1.5]) +def test_category_partitioning(planet, mode, surge): + """Planet × mode × surge cartesian test.""" + result = estimate_delivery_time(planet, mode, surge, no_weather) + assert result > 0 diff --git a/tests/test_string_distance.py b/tests/test_string_distance.py deleted file mode 100644 index 61a6bd3..0000000 --- a/tests/test_string_distance.py +++ /dev/null @@ -1,23 +0,0 @@ - -# Make sure the test finds the application code -import os -import sys -sys.path.insert(0, os.path.abspath('.')) - -import unittest -from app import string_utils - -class TestStringDistance(unittest.TestCase): - - def test_similar_text(self): - score:float = string_utils.calcualte_text_distance("Software engineering is the cornerstone of a successful software project.","Building engineering is the cornerstone of a successful building project.") - self.assertGreater(score, 0.7) - self.assertLess(score,0.8) - - def test_non_similar_text(self): - score:float = string_utils.calcualte_text_distance("Requirements are a first level entity in Agile development","Microservices have helped address scalibility issues in today's cloud environments.") - self.assertGreater(score, 0.3) - self.assertLess(score,0.4) - -if __name__ == "__main__": - unittest.main() \ No newline at end of file diff --git a/tests/test_substring.py b/tests/test_substring.py deleted file mode 100644 index a080210..0000000 --- a/tests/test_substring.py +++ /dev/null @@ -1,21 +0,0 @@ - -# Make sure the test finds the application code -import os -import sys -sys.path.insert(0, os.path.abspath('.')) - -import unittest -from app import string_utils - -class TestSubstring(unittest.TestCase): - - def test_equal_substrings(self): - score:float = string_utils.calculate_match_degree("software engineering","software engineering") - self.assertEqual(score, 1) - - def test_partial_substrings(self): - score:float = string_utils.calculate_match_degree("software engineering","building engineering") - self.assertEqual(score, 0.6) - -if __name__ == "__main__": - unittest.main() \ No newline at end of file From 06b85ecf9dd8b749021e975187b2368df1fa8251 Mon Sep 17 00:00:00 2001 From: Chris Ackermann Date: Fri, 28 Nov 2025 23:59:21 -0500 Subject: [PATCH 04/13] Fixed build errors --- .github/workflows/test.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e307c66..764e878 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,11 +17,16 @@ jobs: with: python-version: "3.11" - - name: Run linter - run: ruff check . - - - name: Check formatting - run: black --check . + - name: Install lint tools + run: pip install flake8 black + + + - name: Run black (check) + run: python -m black --check . + + + - name: Run flake8 + run: flake8 . - name: Install Dependencies run: From d1b4b99ae24cec78ad4e8784c7a04d6190ba6b5b Mon Sep 17 00:00:00 2001 From: Chris Ackermann Date: Sat, 29 Nov 2025 00:04:59 -0500 Subject: [PATCH 05/13] Fixed build errors --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 764e878..56c35d7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: - name: Run black (check) - run: python -m black --check . + run: python -m black --diff . - name: Run flake8 From 828a15d10b8dd98935f3b16a190b0033e1e8f406 Mon Sep 17 00:00:00 2001 From: Chris Ackermann Date: Sat, 29 Nov 2025 00:08:20 -0500 Subject: [PATCH 06/13] Fixed build errors --- .github/workflows/test.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 56c35d7..62db20b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,12 +18,7 @@ jobs: python-version: "3.11" - name: Install lint tools - run: pip install flake8 black - - - - name: Run black (check) - run: python -m black --diff . - + run: pip install flake8 - name: Run flake8 run: flake8 . From d2e879075ab9efbadc8ab4cd959555c88ccbc4df Mon Sep 17 00:00:00 2001 From: Chris Ackermann Date: Sat, 6 Dec 2025 15:36:07 -0500 Subject: [PATCH 07/13] Added new actions --- .github/workflows/deploy.yml | 26 +++++++ .github/workflows/release.yml | 41 +++++++++++ .vscode/settings.json | 32 ++++++++- README.md | 32 +++++++++ __version__.py | 7 ++ deployment/update_release_notes.py | 50 +++++++++++++ deployment/update_version_number.py | 70 +++++++++++++++++++ {app => spacedelivery}/delivery.py | 0 spacedelivery/web/estimator.js | 70 +++++++++++++++++++ spacedelivery/web/index.html | 39 +++++++++++ spacedelivery/web/style.css | 54 ++++++++++++++ tests/test_delivery.py | 2 +- workflows-archive/build_docker.yml | 22 ++++++ .../config}/issue-rules.yml | 0 14 files changed, 443 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/deploy.yml create mode 100644 .github/workflows/release.yml create mode 100644 __version__.py create mode 100644 deployment/update_release_notes.py create mode 100644 deployment/update_version_number.py rename {app => spacedelivery}/delivery.py (100%) create mode 100644 spacedelivery/web/estimator.js create mode 100644 spacedelivery/web/index.html create mode 100644 spacedelivery/web/style.css create mode 100644 workflows-archive/build_docker.yml rename {config => workflows-archive/config}/issue-rules.yml (100%) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..f4bb467 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,26 @@ +name: Deploy to GitHub Pages + +on: + push: + branches: ["main"] + +permissions: + contents: read + pages: write + id-token: write + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout source + uses: actions/checkout@v4 + + - name: Upload static site + uses: actions/upload-pages-artifact@v2 + with: + path: . + + - name: Deploy to GitHub Pages + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..98bb66f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,41 @@ +name: Add Release Notes to README + +on: + pull_request: + types: [opened] + +jobs: + update_readme: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - name: Increase version number + env: + PR_TITLE: ${{ github.event.pull_request.title }} + run: | + python deployment/update_version_number.py + + - name: Update release notes + env: + PR_TITLE: ${{ github.event.pull_request.title }} + PR_BODY: ${{ github.event.pull_request.body }} + run: | + python deployment/update_release_notes.py + + - name: Commit changes + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add README.md + git commit -m "Add release notes for PR #${{ github.event.pull_request.number }}" || echo "No changes to commit" + git push diff --git a/.vscode/settings.json b/.vscode/settings.json index 8fc7789..37e28eb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,5 +12,35 @@ "coverage-gutters.showLineCoverage": true, "python.testing.pytestArgs": [ "tests" - ] + ], + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "**/Thumbs.db": true, + "**/*.pyc": { + "when": "$(basename).py" + }, + "**/__pycache__": true, + "**/app.egg-info": true, + "**/env": true, + "**/.env.dist": true, + "**/*.log": true, + "**/.0": true + }, + "workbench.colorCustomizations": { + "tab.activeBorder": "#ff0000", + "tab.unfocusedActiveBorder": "#000000", + "tab.activeBackground": "#045980" + }, + "workbench.editor.wrapTabs": true, + "debug.toolBarLocation": "docked", + "python.formatting.provider": "autopep8", + "editor.formatOnSave": true, + "[python]": { + "editor.defaultFormatter": "ms-python.autopep8" + }, + "python.REPL.enableREPLSmartSend": false } \ No newline at end of file diff --git a/README.md b/README.md index f283c09..a80ea8f 100644 --- a/README.md +++ b/README.md @@ -125,3 +125,35 @@ After you created the file and copied the action above, push the change to the r * **Task C**: Add a test case to either test file and push your changes to your repository. Check the run of the action to see what status is finishes with. * **Task D**: You will notice that the action shows a red x after it has completed its run. Investigate why that action failed. Resolve the issue and push to the repository to trigger the action again. + + + + +Task 1 + +Automated testing on commit + +a) Create a new branch called `test` and push it to the repository. Then create a PR for that branch. +b) Observe the test results +c) Introduce a bug and push. Observe the test results. + + + +Task 2 + +Add release notes to README + +a) Create a new branch called `release` and push it to the repository. +b) Then create a PR for that branch and add a bulleted list of changes. +b) Check that the release notes are added to the `README.md` file. + + +Task 3 + +Deploy on merge + +a) Create a new branch called `deploy1` and push it to the repository. Then create a PR for that branch. +b) Merge the PR into `main`. +c) Navigate to the deployed app +d) Change the app title and create a new PR. Merge the PR into `main`. Navigate to the deployed app again.Observe the changes. + diff --git a/__version__.py b/__version__.py new file mode 100644 index 0000000..c046acc --- /dev/null +++ b/__version__.py @@ -0,0 +1,7 @@ +# coding: utf-8 + +__title__ = 'enpm611-ghactions' +__version__ = '1.3.8' +__author__ = 'ENPM611' +__url__ = 'https://github.com/enpm611/github-actions' +__description__ = ("Exercise to use GitHub Actions for CI/CD task.") diff --git a/deployment/update_release_notes.py b/deployment/update_release_notes.py new file mode 100644 index 0000000..20e6112 --- /dev/null +++ b/deployment/update_release_notes.py @@ -0,0 +1,50 @@ + + +import os +import re +from datetime import datetime +from pathlib import Path + + +def get_current_version() -> str: + # Read version file + VERSION_FILE = Path("__version__.py") + version_text = VERSION_FILE.read_text() + # Extract current version string + match = re.search(r"__version__\s*=\s*['\"](\d+\.\d+\.\d+)['\"]", version_text) + if not match: + raise ValueError("Could not find __version__ in file.") + return match.group(1) + + +def update_release_notes() -> None: + + # Read the environment variables that were passed in from + # the GitHub Action workflow. + pr_title: str = os.environ.get("PR_TITLE", "").strip() + pr_body: str = os.environ.get("PR_BODY", "").strip() + # Check for missing environment variables + if not pr_title: + raise ValueError("PR_TITLE environment variable is missing.") + + # Get current version from version file + version: str = get_current_version() + + # Create release note entry + date_str: str = datetime.utcnow().strftime("%Y-%m-%d") + entry_lines: list[str] = [ + "", + f"## Release Notes — v{version} — {pr_title} ({date_str})", + "", + pr_body, + "", + ] + + # Append to README + with open("README.md", "a", encoding="utf-8") as f: + f.write("\n".join(entry_lines)) + + print("README updated with release notes.") + +if __name__ == "__main__": + update_release_notes() diff --git a/deployment/update_version_number.py b/deployment/update_version_number.py new file mode 100644 index 0000000..7ad249a --- /dev/null +++ b/deployment/update_version_number.py @@ -0,0 +1,70 @@ + +""" +Bumps the version number in the `__version__.py` file when a Pull +Request is created. +""" + + +import re +import os +from pathlib import Path +from typing import Any, Literal + + +def get_current_version(version_text:str) -> str: + # Extract current version string + match = re.search(r"__version__\s*=\s*['\"](\d+\.\d+\.\d+)['\"]", version_text) + if not match: + raise ValueError("Could not find __version__ in file.") + return match.group(1) + +def get_release_type() -> Literal["patch", "minor", "major"]: + """ Determine the type of release based on the PR title """ + + # Read release title from env + pr_title: str = os.environ.get("PR_TITLE", "").strip() + # Determine release type based on terms in title + if "minor" in pr_title.lower(): + return "minor" + elif "major" in pr_title.lower(): + return "major" + else: + return "patch" + +def bump_version(): + + # Read version file + VERSION_FILE = Path("__version__.py") + version_text = VERSION_FILE.read_text() + + old_version: str = get_current_version(version_text) + major, minor, patch = map(int, old_version.split(".")) + + # Increment based on the requested level + level: Literal['patch', 'minor', 'major'] = get_release_type() + if level == "patch": + patch += 1 + elif level == "minor": + minor += 1 + patch = 0 + elif level == "major": + major += 1 + minor = 0 + patch = 0 + else: + raise ValueError("level must be 'major', 'minor', or 'patch'") + + new_version = f"{major}.{minor}.{patch}" + + # Replace the version string in the file + new_text = version_text.replace(old_version,new_version) + + VERSION_FILE.write_text(new_text) + print(f"Version bumped to {new_version}") + return new_version + + + + +if __name__ == "__main__": + bump_version() diff --git a/app/delivery.py b/spacedelivery/delivery.py similarity index 100% rename from app/delivery.py rename to spacedelivery/delivery.py diff --git a/spacedelivery/web/estimator.js b/spacedelivery/web/estimator.js new file mode 100644 index 0000000..d456db9 --- /dev/null +++ b/spacedelivery/web/estimator.js @@ -0,0 +1,70 @@ +const PLANET_DISTANCE = { + Mercury: 3, + Venus: 2, + Earth: 0.5, + Mars: 4, + Jupiter: 25, + Saturn: 50, + Neptune: 200, +}; + +const MODE_SPEED = { + 1: 10, // NORMAL + 2: 25, // TURBO + 3: 100, // HYPERJUMP +}; + +// Populate planet dropdown +window.onload = () => { + const select = document.getElementById("planet"); + Object.keys(PLANET_DISTANCE).forEach((planet) => { + const option = document.createElement("option"); + option.value = planet; + option.textContent = planet; + select.appendChild(option); + }); +}; + +function estimateDeliveryTime(planet, mode, surgeLoad, weatherDelay) { + if (!(planet in PLANET_DISTANCE)) { + throw new Error("Unknown destination"); + } + if (!(mode in MODE_SPEED)) { + throw new Error("Invalid delivery mode"); + } + if (surgeLoad < 1) { + throw new Error("surgeLoad must be >= 1"); + } + if (weatherDelay < 0) { + throw new Error("weatherDelay must be >= 0"); + } + + const distance = PLANET_DISTANCE[planet]; + const speed = MODE_SPEED[mode]; + + let travelTime = distance / speed; + + // Fatigue penalty for extreme distances + if (distance > 100) { + travelTime *= 1.2; + } + + const total = travelTime * surgeLoad + weatherDelay; + return Math.round(total * 100) / 100; +} + +// Wire UI to estimator +document.getElementById("estimate").onclick = () => { + const planet = document.getElementById("planet").value; + const mode = Number(document.getElementById("mode").value); + const surge = Number(document.getElementById("surge").value); + const weather = Number(document.getElementById("weather").value); + + try { + const time = estimateDeliveryTime(planet, mode, surge, weather); + document.getElementById("result").textContent = + `Estimated delivery time: ${time} hours`; + } catch (err) { + document.getElementById("result").textContent = "Error: " + err.message; + } +}; diff --git a/spacedelivery/web/index.html b/spacedelivery/web/index.html new file mode 100644 index 0000000..df4c568 --- /dev/null +++ b/spacedelivery/web/index.html @@ -0,0 +1,39 @@ + + + + + + Galactic Pizza Delivery Estimator + + + + +
+

🚀 Galactic Pizza Delivery Estimator

+ +
+ + + + + + + + + + + + + +
+ +
+
+ + + + diff --git a/spacedelivery/web/style.css b/spacedelivery/web/style.css new file mode 100644 index 0000000..26363cd --- /dev/null +++ b/spacedelivery/web/style.css @@ -0,0 +1,54 @@ +body { + font-family: Arial, sans-serif; + background: #0f0f22; + color: #fff; + margin: 0; + padding: 0; +} + +.container { + max-width: 500px; + margin: 50px auto; + background: #1c1c3c; + padding: 20px; + border-radius: 10px; +} + +h1 { + text-align: center; + margin-bottom: 20px; +} + +label { + font-weight: bold; + margin-top: 10px; + display: block; +} + +input, select { + width: 100%; + padding: 8px; + margin-top: 4px; + border-radius: 5px; + border: none; +} + +button { + width: 100%; + padding: 10px; + margin-top: 15px; + background: #00b7ff; + border: none; + border-radius: 5px; + font-size: 16px; + cursor: pointer; +} + +button:hover { + background: #0090cc; +} + +.result { + margin-top: 20px; + font-size: 18px; +} diff --git a/tests/test_delivery.py b/tests/test_delivery.py index ca954e0..94ec226 100644 --- a/tests/test_delivery.py +++ b/tests/test_delivery.py @@ -5,7 +5,7 @@ sys.path.insert(0, os.path.abspath('.')) import pytest -from app.delivery import estimate_delivery_time, DeliveryMode +from spacedelivery.delivery import estimate_delivery_time, DeliveryMode def no_weather(): diff --git a/workflows-archive/build_docker.yml b/workflows-archive/build_docker.yml new file mode 100644 index 0000000..a470636 --- /dev/null +++ b/workflows-archive/build_docker.yml @@ -0,0 +1,22 @@ +name: Build and save Docker image artifact + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Build the Docker image + run: docker build . --tag spacedelivery:latest + + - name: Save image to a tar file + run: docker save spacedelivery:latest -o ${{ runner.temp }}/spacedelivery.tar + + - name: Upload Docker image artifact + uses: actions/upload-artifact@v4 + with: + name: docker-image-artifact + path: ${{ runner.temp }}/spacedelivery.tar \ No newline at end of file diff --git a/config/issue-rules.yml b/workflows-archive/config/issue-rules.yml similarity index 100% rename from config/issue-rules.yml rename to workflows-archive/config/issue-rules.yml From 6dfa377693c457ac25eec5dbd64cca4c7ef2cccb Mon Sep 17 00:00:00 2001 From: Chris Ackermann Date: Sat, 6 Dec 2025 15:37:15 -0500 Subject: [PATCH 08/13] Removed linting from action --- .github/workflows/test.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 62db20b..fe6ece7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,16 +17,8 @@ jobs: with: python-version: "3.11" - - name: Install lint tools - run: pip install flake8 - - - name: Run flake8 - run: flake8 . - - name: Install Dependencies - run: - pip install -r requirements.txt + run: pip install -r requirements.txt - name: Run tests run: pytest -q - From 46d112a73538c038fbd494484b96b2f2430c9371 Mon Sep 17 00:00:00 2001 From: Chris Ackermann Date: Sat, 6 Dec 2025 15:38:30 -0500 Subject: [PATCH 09/13] Added a bug --- tests/test_delivery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_delivery.py b/tests/test_delivery.py index 94ec226..908388d 100644 --- a/tests/test_delivery.py +++ b/tests/test_delivery.py @@ -22,7 +22,7 @@ def fixed_weather(): def test_basic_delivery_normal_mode(): """Earth is 0.5 lm away; NORMAL speed 10 lm/hr → 0.05 hr.""" - assert estimate_delivery_time("Earth", DeliveryMode.NORMAL, 1.0, no_weather) == 0.05 + assert estimate_delivery_time("Earth", DeliveryMode.NORMAL, 1.0, no_weather) == 0.06 def test_turbo_delivery(): From 962222f27d8dab47b276e6403577259a2983cd67 Mon Sep 17 00:00:00 2001 From: Chris Ackermann Date: Sat, 6 Dec 2025 15:46:49 -0500 Subject: [PATCH 10/13] Fixed the release action --- .github/workflows/release.yml | 2 +- README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 98bb66f..e23b047 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,7 +12,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 with: - fetch-depth: 0 + ref: ${{ github.head_ref }} - name: Set up Python uses: actions/setup-python@v5 diff --git a/README.md b/README.md index a80ea8f..a785831 100644 --- a/README.md +++ b/README.md @@ -134,8 +134,8 @@ Task 1 Automated testing on commit a) Create a new branch called `test` and push it to the repository. Then create a PR for that branch. -b) Observe the test results -c) Introduce a bug and push. Observe the test results. +b) Observe the test results. If there is a bug, update the test case to match the value that is returned by the function. +c) Push the updated code. Observe the test results. From 581a07d2b0b93f5e00fc8bed0675326d3bbeaf64 Mon Sep 17 00:00:00 2001 From: Chris Ackermann Date: Sat, 6 Dec 2025 15:47:38 -0500 Subject: [PATCH 11/13] Removed bug --- tests/test_delivery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_delivery.py b/tests/test_delivery.py index 908388d..94ec226 100644 --- a/tests/test_delivery.py +++ b/tests/test_delivery.py @@ -22,7 +22,7 @@ def fixed_weather(): def test_basic_delivery_normal_mode(): """Earth is 0.5 lm away; NORMAL speed 10 lm/hr → 0.05 hr.""" - assert estimate_delivery_time("Earth", DeliveryMode.NORMAL, 1.0, no_weather) == 0.06 + assert estimate_delivery_time("Earth", DeliveryMode.NORMAL, 1.0, no_weather) == 0.05 def test_turbo_delivery(): From 593ac4d5b563e9d70da99d1885eb07cf4d59158d Mon Sep 17 00:00:00 2001 From: Chris Ackermann Date: Sat, 6 Dec 2025 15:52:06 -0500 Subject: [PATCH 12/13] Added permissions for action --- .github/workflows/release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e23b047..02a8452 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,9 @@ on: pull_request: types: [opened] +permissions: + contents: write + pull-requests: write jobs: update_readme: runs-on: ubuntu-latest From 6f70d1d7f15356e170f4bf7375ee24ae4baa6ced Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 6 Dec 2025 20:52:51 +0000 Subject: [PATCH 13/13] Add release notes for PR #12 --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index a785831..ada291d 100644 --- a/README.md +++ b/README.md @@ -157,3 +157,8 @@ b) Merge the PR into `main`. c) Navigate to the deployed app d) Change the app title and create a new PR. Merge the PR into `main`. Navigate to the deployed app again.Observe the changes. + +## Release Notes — v2.0.0 — Major changes (2025-12-06) + +- Big deal changes +- Minor bug fixes