Skip to content

Commit feabff7

Browse files
committed
feat: initial scaffold for cq-demo-app-002
0 parents  commit feabff7

33 files changed

Lines changed: 1358 additions & 0 deletions

.coverage

52 KB
Binary file not shown.

.github/workflows/deploy.yml

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
name: Deploy
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
8+
permissions:
9+
id-token: write
10+
contents: read
11+
12+
env:
13+
AZURE_RG: rg-cq-demo-002
14+
APP_NAME: cq-demo-app-002
15+
LOCATION: canadacentral
16+
17+
jobs:
18+
deploy:
19+
runs-on: ubuntu-latest
20+
environment: prod
21+
steps:
22+
- uses: actions/checkout@v4
23+
24+
- name: Azure Login
25+
uses: azure/login@v2
26+
with:
27+
client-id: ${{ secrets.AZURE_CLIENT_ID }}
28+
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
29+
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
30+
31+
- name: Create Resource Group
32+
run: az group create --name ${{ env.AZURE_RG }} --location ${{ env.LOCATION }}
33+
34+
- name: Deploy Infrastructure
35+
id: infra
36+
run: |
37+
outputs=$(az deployment group create \
38+
--resource-group ${{ env.AZURE_RG }} \
39+
--template-file infra/main.bicep \
40+
--parameters appName=${{ env.APP_NAME }} \
41+
--query 'properties.outputs' -o json)
42+
echo "acrName=$(echo $outputs | jq -r '.acrName.value')" >> $GITHUB_OUTPUT
43+
echo "appServiceName=$(echo $outputs | jq -r '.appServiceName.value')" >> $GITHUB_OUTPUT
44+
45+
- name: Build and Push to ACR
46+
run: |
47+
az acr build \
48+
--registry ${{ steps.infra.outputs.acrName }} \
49+
--image ${{ env.APP_NAME }}:${{ github.sha }} \
50+
--image ${{ env.APP_NAME }}:latest \
51+
.
52+
53+
- name: Deploy to Web App for Containers
54+
uses: azure/webapps-deploy@v3
55+
with:
56+
app-name: ${{ steps.infra.outputs.appServiceName }}
57+
images: ${{ steps.infra.outputs.acrName }}.azurecr.io/${{ env.APP_NAME }}:${{ github.sha }}
58+
59+
- name: Health Check
60+
run: |
61+
APP_URL=$(az webapp show -g ${{ env.AZURE_RG }} -n ${{ steps.infra.outputs.appServiceName }} --query defaultHostName -o tsv)
62+
sleep 30
63+
curl -sf --retry 5 --retry-delay 10 "https://$APP_URL/health" || exit 1
64+
65+
- name: Summary
66+
run: |
67+
APP_URL=$(az webapp show -g ${{ env.AZURE_RG }} -n ${{ steps.infra.outputs.appServiceName }} --query defaultHostName -o tsv)
68+
echo "### ✅ ${{ env.APP_NAME }}" >> $GITHUB_STEP_SUMMARY
69+
echo "Deployed to: https://$APP_URL" >> $GITHUB_STEP_SUMMARY

Dockerfile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# ---------- Build stage ----------
2+
FROM python:3.12-slim AS builder
3+
WORKDIR /app
4+
COPY requirements.txt .
5+
RUN pip install --no-cache-dir -r requirements.txt
6+
7+
# ---------- Runtime stage ----------
8+
FROM python:3.12-slim
9+
WORKDIR /app
10+
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
11+
COPY --from=builder /usr/local/bin /usr/local/bin
12+
COPY src/ ./src/
13+
EXPOSE 5000
14+
CMD ["python", "-m", "flask", "--app", "src/app", "run", "--host=0.0.0.0", "--port=5000"]

README.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# cq-demo-app-002 — Inventory Management API (Python / Flask)
2+
3+
A Python/Flask REST API for inventory management. This demo app contains **intentional code quality violations** for use with the Code Quality Scanner in the Agentic Accelerator Framework.
4+
5+
## Intentional Violations
6+
7+
| Category | File(s) | Description |
8+
|----------|---------|-------------|
9+
| **High Complexity** | `src/services/inventory_service.py` | `process_inventory_update()` has CCN > 15 with deeply nested if/elif/else for stock levels, warehouse zones, reorder thresholds, and supplier routing |
10+
| **High Complexity** | `src/routes/inventory.py` | `list_inventory()` has deeply nested filter logic with 7+ nesting levels |
11+
| **Code Duplication** | `src/services/report_service.py``src/services/inventory_service.py` | Nearly identical aggregation logic with minor field name changes |
12+
| **Code Duplication** | `src/routes/reports.py``src/routes/inventory.py` | Repeated filtering and sorting patterns |
13+
| **Lint — Unused Imports** | `src/utils/validators.py`, `src/services/*.py`, `src/utils/formatters.py` | `os`, `sys`, `hashlib`, `math`, `json`, `re` imported but never used |
14+
| **Lint — Broad Exceptions** | `src/utils/validators.py` | `except Exception:` and bare `except:` used throughout |
15+
| **Lint — Missing Docstrings** | `src/utils/formatters.py` | All functions lack docstrings |
16+
| **Lint — Naming** | `src/utils/formatters.py` | `Format_Alert_Badge()`, `FormatReorderEstimate()` violate snake_case convention |
17+
| **Lint — Mutable Default** | `src/utils/validators.py` | `validate_inventory_input(data, allowed_fields=[...])` uses mutable default argument |
18+
| **Lint — format() vs f-string** | `src/utils/formatters.py` | Uses `"{}".format()` instead of f-strings throughout |
19+
| **Low Coverage** | `tests/test_app.py` | Only 3 tests covering health, index, and list. Services and utils are completely untested (~30% coverage) |
20+
21+
## API Endpoints
22+
23+
| Method | Path | Description |
24+
|--------|------|-------------|
25+
| GET | `/` | App info |
26+
| GET | `/health` | Health check |
27+
| GET | `/api/inventory/` | List inventory (query params: `warehouse`, `zone`, `category`, `low_stock`, `sort_by`, `order`) |
28+
| GET | `/api/inventory/<id>` | Get item by ID |
29+
| POST | `/api/inventory/<id>/update` | Update stock (body: `action`, `quantity`, `reason`, `target_warehouse`, `priority`) |
30+
| GET | `/api/inventory/summary` | Inventory summary by warehouse |
31+
| GET | `/api/inventory/reorder` | Reorder recommendations |
32+
| GET | `/api/reports/inventory` | Inventory status report |
33+
| GET | `/api/reports/warehouse-utilization` | Warehouse utilization report |
34+
| GET | `/api/reports/supplier-performance` | Supplier performance report |
35+
| GET | `/api/reports/stock-alerts` | Stock alert report |
36+
37+
## Run Locally
38+
39+
Build and run with Docker (works in GitHub Codespaces):
40+
41+
```bash
42+
docker build -t cq-demo-app-002 .
43+
docker run -p 5000:5000 cq-demo-app-002
44+
```
45+
46+
Then open [http://localhost:5000](http://localhost:5000) to see the app info, or try:
47+
48+
```bash
49+
curl http://localhost:5000/health
50+
curl http://localhost:5000/api/inventory/
51+
curl http://localhost:5000/api/inventory/1
52+
curl http://localhost:5000/api/inventory/summary
53+
curl http://localhost:5000/api/inventory/reorder
54+
curl http://localhost:5000/api/reports/stock-alerts
55+
```
56+
57+
### Run without Docker
58+
59+
```bash
60+
pip install -r requirements.txt
61+
python -m flask --app src/app run --host=0.0.0.0 --port=5000
62+
```
63+
64+
### Run Tests
65+
66+
```bash
67+
pip install -r requirements.txt
68+
pytest tests/ -v --cov=src --cov-report=term-missing
69+
```
70+
71+
## Scanning
72+
73+
This app is designed to produce findings with:
74+
75+
- **Ruff** — unused imports, missing docstrings, broad exceptions, naming conventions, mutable defaults, format vs f-string
76+
- **Lizard** — high cyclomatic complexity (CCN > 15) in `process_inventory_update()`
77+
- **jscpd** — code duplication between `inventory_service.py` / `report_service.py` and `inventory.py` / `reports.py`
78+
- **pytest-cov** — low test coverage (< 50%)
79+
80+
## Tech Stack
81+
82+
- Python 3.12
83+
- Flask 3.x
84+
- pytest + pytest-cov
85+
- Docker (multi-stage build)
86+
- Azure Web App for Containers (via Bicep + ACR)

infra/main.bicep

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
@description('Logical app name used for resource naming')
2+
param appName string = 'cq-demo-app-002'
3+
4+
@description('Azure region for all resources')
5+
param location string = resourceGroup().location
6+
7+
var suffix = uniqueString(resourceGroup().id)
8+
var acrName = 'acr${suffix}'
9+
var appServicePlanName = 'plan-${appName}-${suffix}'
10+
var appServiceName = '${appName}-${suffix}'
11+
12+
resource acr 'Microsoft.ContainerRegistry/registries@2023-07-01' = {
13+
name: acrName
14+
location: location
15+
sku: {
16+
name: 'Basic'
17+
}
18+
properties: {
19+
adminUserEnabled: true
20+
}
21+
}
22+
23+
resource appServicePlan 'Microsoft.Web/serverfarms@2023-12-01' = {
24+
name: appServicePlanName
25+
location: location
26+
kind: 'linux'
27+
sku: {
28+
name: 'B1'
29+
tier: 'Basic'
30+
}
31+
properties: {
32+
reserved: true
33+
}
34+
}
35+
36+
resource webApp 'Microsoft.Web/sites@2023-12-01' = {
37+
name: appServiceName
38+
location: location
39+
kind: 'app,linux,container'
40+
identity: {
41+
type: 'SystemAssigned'
42+
}
43+
properties: {
44+
serverFarmId: appServicePlan.id
45+
siteConfig: {
46+
linuxFxVersion: 'DOCKER|${acr.properties.loginServer}/${appName}:latest'
47+
appSettings: [
48+
{
49+
name: 'DOCKER_REGISTRY_SERVER_URL'
50+
value: 'https://${acr.properties.loginServer}'
51+
}
52+
{
53+
name: 'DOCKER_REGISTRY_SERVER_USERNAME'
54+
value: acr.listCredentials().username
55+
}
56+
{
57+
name: 'DOCKER_REGISTRY_SERVER_PASSWORD'
58+
value: acr.listCredentials().passwords[0].value
59+
}
60+
{
61+
name: 'WEBSITES_ENABLE_APP_SERVICE_STORAGE'
62+
value: 'false'
63+
}
64+
]
65+
}
66+
}
67+
}
68+
69+
output acrName string = acr.name
70+
output acrLoginServer string = acr.properties.loginServer
71+
output appServiceName string = webApp.name
72+
output appUrl string = 'https://${webApp.properties.defaultHostName}'

pyproject.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[project]
2+
name = "cq-demo-app-002"
3+
version = "1.0.0"
4+
description = "Inventory Management API — Python/Flask demo app with intentional code quality violations"
5+
requires-python = ">=3.12"
6+
dependencies = [
7+
"flask>=3.0,<4.0",
8+
]
9+
10+
[tool.ruff]
11+
line-length = 120
12+
target-version = "py312"
13+
14+
[tool.ruff.lint]
15+
select = ["E", "F", "W", "I", "N", "D", "UP"]
16+
17+
[tool.ruff.lint.pydocstyle]
18+
convention = "google"
19+
20+
[tool.pytest.ini_options]
21+
testpaths = ["tests"]

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
flask>=3.0,<4.0
2+
pytest>=8.0,<9.0
3+
pytest-cov>=5.0,<6.0
1.49 KB
Binary file not shown.
3.46 KB
Binary file not shown.

src/app.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from flask import Flask
2+
import os, logging
3+
4+
from src.routes.inventory import inventory_bp
5+
from src.routes.reports import reports_bp
6+
7+
8+
def create_app():
9+
app = Flask(__name__)
10+
11+
app.config["SECRET_KEY"] = "super-secret-key-hardcoded" # noqa: S105 — Intentional: hardcoded secret
12+
app.config["DEBUG"] = True # Intentional: debug mode enabled
13+
14+
app.register_blueprint(inventory_bp, url_prefix="/api/inventory")
15+
app.register_blueprint(reports_bp, url_prefix="/api/reports")
16+
17+
@app.route("/")
18+
def index():
19+
return {"status": "ok", "app": "cq-demo-app-002", "version": "1.0.0"}
20+
21+
@app.route("/health")
22+
def health():
23+
return {"status": "healthy"}
24+
25+
return app
26+
27+
28+
app = create_app()
29+
30+
if __name__ == "__main__":
31+
app.run(host="0.0.0.0", port=5000, debug=True)

0 commit comments

Comments
 (0)