Skip to content

Conversation

@zMynxx
Copy link
Contributor

@zMynxx zMynxx commented Oct 18, 2025

PR Type

Enhancement, Tests


Description

  • Added AWS Lambda free tier support with configurable toggle

    • 1M free requests per month
    • 400,000 GB-seconds free compute time per month
  • Updated pricing calculation functions to apply free tier deductions

    • calc_monthly_compute_charges() now deducts free compute GB-seconds
    • calc_monthly_request_charges() now deducts free requests
  • Added comprehensive test coverage for free tier functionality

    • 262 new tests covering edge cases and coverage gaps
    • Tests for all request unit conversions and tiered pricing scenarios
  • Enhanced UI with free tier information banner in demo page


Diagram Walkthrough

flowchart LR
  A["User Input<br/>include_free_tier"] --> B["CalculationRequest<br/>Model"]
  B --> C["calculate()"]
  C --> D["calc_monthly_compute_charges()"]
  C --> E["calc_monthly_request_charges()"]
  D --> F["Apply Free Tier<br/>400K GB-seconds"]
  E --> G["Apply Free Tier<br/>1M Requests"]
  F --> H["Billable Compute"]
  G --> I["Billable Requests"]
  H --> J["Total Cost"]
  I --> J
  J --> K["Demo UI<br/>Free Tier Banner"]
Loading

File Walkthrough

Relevant files
Enhancement
calculator.py
Implement free tier deduction in pricing calculations       

aws-lambda-calculator/src/aws_lambda_calculator/calculator.py

  • Added include_free_tier parameter to calc_monthly_compute_charges()
    function
  • Implemented free tier deduction logic for compute: 400,000 GB-seconds
    per month
  • Added include_free_tier parameter to calc_monthly_request_charges()
    function
  • Implemented free tier deduction logic for requests: 1,000,000 requests
    per month
  • Updated calculate() function to accept and pass through
    include_free_tier parameter (defaults to True)
  • Enhanced logging and calculation steps to show free tier deductions
+44/-13 
models.py
Add free tier toggle to request model                                       

aws-lambda-calculator/src/aws_lambda_calculator/models.py

  • Added include_free_tier field to CalculationRequest model
  • Field defaults to True to enable free tier by default
  • Includes description documenting AWS Lambda free tier benefits
+4/-0     
demo.tsx
Add free tier information banner to demo UI                           

site/app/routes/demo.tsx

  • Added informational banner that displays when free tier is enabled
  • Banner shows free tier limits: 1M requests and 400K GB-seconds per
    month
  • Banner styled with blue background and icon for visibility
  • Includes dark mode support for the banner styling
  • Banner conditionally renders based on include_free_tier form data
    value
+21/-0   
Tests
test_free_tier.py
Add comprehensive free tier test coverage                               

tests/test_free_tier.py

  • Created comprehensive test suite for free tier functionality (262
    lines)
  • Tests cover scenarios: usage under limits, exceeding request limits,
    exceeding compute limits
  • Tests verify free tier can be disabled and disabled correctly
  • Tests validate edge cases like exact limit boundaries
  • Tests verify free tier works across different architectures (x86,
    arm64)
  • Tests include high-usage scenarios to ensure proper billing
    calculations
+262/-0 
test_coverage_gaps.py
Add coverage gap tests for calculator functions                   

tests/test_coverage_gaps.py

  • Created new test file to improve code coverage (263 lines)
  • Tests cover error paths: invalid regions, invalid unit conversions
  • Tests cover edge cases in unit conversions (GB units, no conversion
    needed)
  • Tests validate tiered cost calculations at boundaries and with
    overflow
  • Tests cover multiple tier structures and massive usage scenarios
  • Tests verify all request unit conversions work correctly
+263/-0 
test_edge_cases.py
Update edge case test for free tier parameter                       

tests/test_edge_cases.py

  • Updated test_very_long_duration() to explicitly disable free tier
  • Added include_free_tier=False parameter to test actual pricing without
    free tier benefits
+1/-0     

Copilot AI review requested due to automatic review settings October 18, 2025 01:09
@codiumai-pr-agent-free
Copy link
Contributor

codiumai-pr-agent-free bot commented Oct 18, 2025

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
- [ ] Create ticket/issue <!-- /create_ticket --create_ticket=true -->

</details></td></tr>
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
No custom compliance provided

Follow the guide to enable custom compliance check.

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@github-actions
Copy link
Contributor

github-actions bot commented Oct 18, 2025

☂️ Python Coverage

current status: ✅

Overall Coverage

Lines Covered Coverage Threshold Status
747 742 99% 0% 🟢

New Files

No new covered files...

Modified Files

No covered modified files...

updated for commit: 8788332 by action🐍

@github-actions
Copy link
Contributor

github-actions bot commented Oct 18, 2025

Coverage report

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  aws-lambda-calculator/src/aws_lambda_calculator
  calculator.py
  models.py
  tests
  test_coverage_gaps.py
  test_edge_cases.py
  test_free_tier.py
Project Total  

This report was generated by python-coverage-comment-action

@codiumai-pr-agent-free
Copy link
Contributor

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: unit-tests / Unit Tests

Failed stage: Test the new package [❌]

Failed test name: test_calculate

Failure summary:

The action failed because multiple tests in the AWS Lambda Calculator project are failing due to
calculation discrepancies. The main issues are:

1. In tests/test_calculator.py, multiple test cases are failing because the calculated costs don't
match the expected values. For example, one test expected 56.77 but got 49.91.

2. In tests/test_calculator_coverage.py, the test_calculate_with_arm64_architecture test is failing
because the total_cost is 0.0 when it should be greater than 0.

3. In tests/test_pytest_generate_tests_sample_code.py, all test_calculate_dynamic tests are failing
with similar calculation discrepancies.

These failures suggest that recent changes to the calculator.py implementation have altered the cost
calculation logic, resulting in different values than what the tests expect.

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

549:  env:
550:  pythonLocation: /opt/hostedtoolcache/Python/3.13.8/x64
551:  PKG_CONFIG_PATH: /opt/hostedtoolcache/Python/3.13.8/x64/lib/pkgconfig
552:  Python_ROOT_DIR: /opt/hostedtoolcache/Python/3.13.8/x64
553:  Python2_ROOT_DIR: /opt/hostedtoolcache/Python/3.13.8/x64
554:  Python3_ROOT_DIR: /opt/hostedtoolcache/Python/3.13.8/x64
555:  LD_LIBRARY_PATH: /opt/hostedtoolcache/Python/3.13.8/x64/lib
556:  ##[endgroup]
557:  ============================= test session starts ==============================
558:  platform linux -- Python 3.13.8, pytest-8.3.5, pluggy-1.5.0 -- /home/runner/.cache/pypoetry/virtualenvs/aws-lambda-calculator-tXmV9Ygz-py3.13/bin/python
559:  cachedir: .pytest_cache
560:  rootdir: /home/runner/work/aws-lambda-calculator/aws-lambda-calculator/aws-lambda-calculator
561:  configfile: pyproject.toml
562:  plugins: cov-6.1.0
563:  collecting ... collected 63 items
564:  tests/test_calculator.py::test_calculate[us-east-1-x86-1000000-per day-100-1024-MB-512-MB-56.77] FAILED
565:  tests/test_calculator.py::test_calculate[us-east-1-x86-500000-per month-200-2048-MB-512-MB-3.43] FAILED
566:  tests/test_calculator.py::test_calculate[us-east-1-x86-2000000-per minute-500-1536-MB-512-MB-928523.58] FAILED
567:  tests/test_calculator.py::test_calculate[us-east-1-x86-50000000-per hour-1000-2048-MB-512-MB-1015637.4] FAILED
568:  tests/test_calculator.py::test_calculate[us-east-1-x86-10000000-per minute-1-1024-MB-512-MB-94900.01] FAILED
569:  tests/test_calculator.py::test_calculate[us-east-1-x86-50-per hour-100-1024-MB-5120-MB-0.07] FAILED
570:  tests/test_calculator.py::test_calculate[us-east-1-x86-1000000-per day-100-8192-MB-512-MB-411.64] FAILED
571:  tests/test_calculator.py::test_calculate[us-east-1-x86-5000000-million per month-300-2048-MB-512-MB-41035199.2] FAILED
572:  tests/test_calculator_coverage.py::TestCalculatorFunctionCoverage::test_open_json_file_valid_region PASSED
...

575:  tests/test_calculator_coverage.py::TestCalculatorFunctionCoverage::test_unit_conversion_requests_per_minute PASSED
576:  tests/test_calculator_coverage.py::TestCalculatorFunctionCoverage::test_unit_conversion_requests_per_hour PASSED
577:  tests/test_calculator_coverage.py::TestCalculatorFunctionCoverage::test_unit_conversion_requests_per_day PASSED
578:  tests/test_calculator_coverage.py::TestCalculatorFunctionCoverage::test_unit_conversion_requests_per_month PASSED
579:  tests/test_calculator_coverage.py::TestCalculatorFunctionCoverage::test_unit_conversion_requests_million_per_month PASSED
580:  tests/test_calculator_coverage.py::TestCalculatorFunctionCoverage::test_unit_conversion_requests_invalid_unit PASSED
581:  tests/test_calculator_coverage.py::TestCalculatorFunctionCoverage::test_unit_conversion_memory_mb PASSED
582:  tests/test_calculator_coverage.py::TestCalculatorFunctionCoverage::test_unit_conversion_memory_gb PASSED
583:  tests/test_calculator_coverage.py::TestCalculatorFunctionCoverage::test_unit_conversion_memory_invalid_unit PASSED
584:  tests/test_calculator_coverage.py::TestCalculatorFunctionCoverage::test_unit_conversion_ephemeral_storage_mb PASSED
585:  tests/test_calculator_coverage.py::TestCalculatorFunctionCoverage::test_unit_conversion_ephemeral_storage_gb PASSED
586:  tests/test_calculator_coverage.py::TestCalculatorFunctionCoverage::test_unit_conversion_ephemeral_storage_invalid_unit PASSED
587:  tests/test_calculator_coverage.py::TestCalculatorFunctionCoverage::test_calculate_tiered_cost_simple PASSED
588:  tests/test_calculator_coverage.py::TestCalculatorFunctionCoverage::test_calculate_tiered_cost_with_overflow PASSED
589:  tests/test_calculator_coverage.py::TestCalculatorFunctionCoverage::test_calculate_with_all_unit_combinations PASSED
590:  tests/test_calculator_coverage.py::TestCalculatorFunctionCoverage::test_calculate_with_arm64_architecture FAILED
591:  tests/test_calculator_coverage.py::TestCalculatorFunctionCoverage::test_calculate_with_invalid_architecture PASSED
...

608:  tests/test_models.py::TestCalculationRequest::test_memory_validation_gb_too_low PASSED
609:  tests/test_models.py::TestCalculationRequest::test_memory_validation_gb_too_high PASSED
610:  tests/test_models.py::TestCalculationRequest::test_ephemeral_storage_validation_mb_too_low PASSED
611:  tests/test_models.py::TestCalculationRequest::test_ephemeral_storage_validation_mb_too_high PASSED
612:  tests/test_models.py::TestCalculationRequest::test_ephemeral_storage_validation_gb_too_low PASSED
613:  tests/test_models.py::TestCalculationRequest::test_ephemeral_storage_validation_gb_too_high PASSED
614:  tests/test_models.py::TestCalculationRequest::test_valid_memory_mb_boundary PASSED
615:  tests/test_models.py::TestCalculationRequest::test_valid_memory_gb_boundary PASSED
616:  tests/test_models.py::TestCalculationRequest::test_valid_ephemeral_storage_mb_boundary PASSED
617:  tests/test_models.py::TestCalculationRequest::test_valid_ephemeral_storage_gb_boundary PASSED
618:  tests/test_models.py::TestCalculationRequest::test_arm64_architecture PASSED
619:  tests/test_models.py::TestCalculationResult::test_valid_calculation_result PASSED
620:  tests/test_models.py::TestCalculationResult::test_calculation_result_empty_steps PASSED
621:  tests/test_models.py::TestCalculationResult::test_calculation_result_negative_cost PASSED
622:  tests/test_models.py::TestCalculationResult::test_calculation_result_missing_fields PASSED
623:  tests/test_pytest_generate_tests_sample_code.py::test_calculate_dynamic[params0-81827.82] FAILED
624:  tests/test_pytest_generate_tests_sample_code.py::test_calculate_dynamic[params1-104.5] FAILED
625:  tests/test_pytest_generate_tests_sample_code.py::test_calculate_dynamic[params2-25.51] FAILED
626:  tests/test_pytest_generate_tests_sample_code.py::test_calculate_dynamic[params3-2072.06] FAILED
627:  =================================== FAILURES ===================================
628:  ____ test_calculate[us-east-1-x86-1000000-per day-100-1024-MB-512-MB-56.77] ____
...

735:  ):
736:  result = calculate(
737:  region=region,
738:  architecture=architecture,
739:  number_of_requests=number_of_requests,
740:  request_unit=request_unit,
741:  duration_of_each_request_in_ms=duration_of_each_request_in_ms,
742:  memory=memory,
743:  memory_unit=memory_unit,
744:  ephemeral_storage=ephemeral_storage,
745:  storage_unit=storage_unit,
746:  )
747:  >       assert result.total_cost == approx(expected_cost, abs=0.01), (
748:  f"Expected {expected_cost}, got {result.total_cost}"
749:  )
750:  E       AssertionError: Expected 56.77, got 49.911197922220005
751:  E       assert 49.911197922220005 == 56.77 ± 1.0e-02
752:  E         
753:  E         comparison failed
754:  E         Obtained: 49.911197922220005
755:  E         Expected: 56.77 ± 1.0e-02
756:  tests/test_calculator.py:120: AssertionError
757:  ____ test_calculate[us-east-1-x86-500000-per month-200-2048-MB-512-MB-3.43] ____
...

864:  ):
865:  result = calculate(
866:  region=region,
867:  architecture=architecture,
868:  number_of_requests=number_of_requests,
869:  request_unit=request_unit,
870:  duration_of_each_request_in_ms=duration_of_each_request_in_ms,
871:  memory=memory,
872:  memory_unit=memory_unit,
873:  ephemeral_storage=ephemeral_storage,
874:  storage_unit=storage_unit,
875:  )
876:  >       assert result.total_cost == approx(expected_cost, abs=0.01), (
877:  f"Expected {expected_cost}, got {result.total_cost}"
878:  )
879:  E       AssertionError: Expected 3.43, got 0.0
880:  E       assert 0.0 == 3.43 ± 1.0e-02
881:  E         
882:  E         comparison failed
883:  E         Obtained: 0.0
884:  E         Expected: 3.43 ± 1.0e-02
885:  tests/test_calculator.py:120: AssertionError
886:  _ test_calculate[us-east-1-x86-2000000-per minute-500-1536-MB-512-MB-928523.58] _
...

993:  ):
994:  result = calculate(
995:  region=region,
996:  architecture=architecture,
997:  number_of_requests=number_of_requests,
998:  request_unit=request_unit,
999:  duration_of_each_request_in_ms=duration_of_each_request_in_ms,
1000:  memory=memory,
1001:  memory_unit=memory_unit,
1002:  ephemeral_storage=ephemeral_storage,
1003:  storage_unit=storage_unit,
1004:  )
1005:  >       assert result.total_cost == approx(expected_cost, abs=0.01), (
1006:  f"Expected {expected_cost}, got {result.total_cost}"
1007:  )
1008:  E       AssertionError: Expected 928523.58, got 928518.0466400001
1009:  E       assert 928518.0466400001 == 928523.58 ± 1.0e-02
1010:  E         
1011:  E         comparison failed
1012:  E         Obtained: 928518.0466400001
1013:  E         Expected: 928523.58 ± 1.0e-02
1014:  tests/test_calculator.py:120: AssertionError
1015:  _ test_calculate[us-east-1-x86-50000000-per hour-1000-2048-MB-512-MB-1015637.4] _
...

1122:  ):
1123:  result = calculate(
1124:  region=region,
1125:  architecture=architecture,
1126:  number_of_requests=number_of_requests,
1127:  request_unit=request_unit,
1128:  duration_of_each_request_in_ms=duration_of_each_request_in_ms,
1129:  memory=memory,
1130:  memory_unit=memory_unit,
1131:  ephemeral_storage=ephemeral_storage,
1132:  storage_unit=storage_unit,
1133:  )
1134:  >       assert result.total_cost == approx(expected_cost, abs=0.01), (
1135:  f"Expected {expected_cost}, got {result.total_cost}"
1136:  )
1137:  E       AssertionError: Expected 1015637.4, got 1015631.8666400001
1138:  E       assert 1015631.8666400001 == 1015637.4 ± 1.0e-02
1139:  E         
1140:  E         comparison failed
1141:  E         Obtained: 1015631.8666400001
1142:  E         Expected: 1015637.4 ± 1.0e-02
1143:  tests/test_calculator.py:120: AssertionError
1144:  _ test_calculate[us-east-1-x86-10000000-per minute-1-1024-MB-512-MB-94900.01] __
...

1251:  ):
1252:  result = calculate(
1253:  region=region,
1254:  architecture=architecture,
1255:  number_of_requests=number_of_requests,
1256:  request_unit=request_unit,
1257:  duration_of_each_request_in_ms=duration_of_each_request_in_ms,
1258:  memory=memory,
1259:  memory_unit=memory_unit,
1260:  ephemeral_storage=ephemeral_storage,
1261:  storage_unit=storage_unit,
1262:  )
1263:  >       assert result.total_cost == approx(expected_cost, abs=0.01), (
1264:  f"Expected {expected_cost}, got {result.total_cost}"
1265:  )
1266:  E       AssertionError: Expected 94900.01, got 94893.14792
1267:  E       assert 94893.14792 == 94900.01 ± 1.0e-02
1268:  E         
1269:  E         comparison failed
1270:  E         Obtained: 94893.14792
1271:  E         Expected: 94900.01 ± 1.0e-02
1272:  tests/test_calculator.py:120: AssertionError
1273:  ______ test_calculate[us-east-1-x86-50-per hour-100-1024-MB-5120-MB-0.07] ______
...

1380:  ):
1381:  result = calculate(
1382:  region=region,
1383:  architecture=architecture,
1384:  number_of_requests=number_of_requests,
1385:  request_unit=request_unit,
1386:  duration_of_each_request_in_ms=duration_of_each_request_in_ms,
1387:  memory=memory,
1388:  memory_unit=memory_unit,
1389:  ephemeral_storage=ephemeral_storage,
1390:  storage_unit=storage_unit,
1391:  )
1392:  >       assert result.total_cost == approx(expected_cost, abs=0.01), (
1393:  f"Expected {expected_cost}, got {result.total_cost}"
1394:  )
1395:  E       AssertionError: Expected 0.07, got 0.0005075324999999999
1396:  E       assert 0.0005075324999999999 == 0.07 ± 1.0e-02
1397:  E         
1398:  E         comparison failed
1399:  E         Obtained: 0.0005075324999999999
1400:  E         Expected: 0.07 ± 1.0e-02
1401:  tests/test_calculator.py:120: AssertionError
1402:  ___ test_calculate[us-east-1-x86-1000000-per day-100-8192-MB-512-MB-411.64] ____
...

1509:  ):
1510:  result = calculate(
1511:  region=region,
1512:  architecture=architecture,
1513:  number_of_requests=number_of_requests,
1514:  request_unit=request_unit,
1515:  duration_of_each_request_in_ms=duration_of_each_request_in_ms,
1516:  memory=memory,
1517:  memory_unit=memory_unit,
1518:  ephemeral_storage=ephemeral_storage,
1519:  storage_unit=storage_unit,
1520:  )
1521:  >       assert result.total_cost == approx(expected_cost, abs=0.01), (
1522:  f"Expected {expected_cost}, got {result.total_cost}"
1523:  )
1524:  E       AssertionError: Expected 411.64, got 404.77301097776
1525:  E       assert 404.77301097776 == 411.64 ± 1.0e-02
1526:  E         
1527:  E         comparison failed
1528:  E         Obtained: 404.77301097776
1529:  E         Expected: 411.64 ± 1.0e-02
1530:  tests/test_calculator.py:120: AssertionError
1531:  _ test_calculate[us-east-1-x86-5000000-million per month-300-2048-MB-512-MB-41035199.2] _
...

1638:  ):
1639:  result = calculate(
1640:  region=region,
1641:  architecture=architecture,
1642:  number_of_requests=number_of_requests,
1643:  request_unit=request_unit,
1644:  duration_of_each_request_in_ms=duration_of_each_request_in_ms,
1645:  memory=memory,
1646:  memory_unit=memory_unit,
1647:  ephemeral_storage=ephemeral_storage,
1648:  storage_unit=storage_unit,
1649:  )
1650:  >       assert result.total_cost == approx(expected_cost, abs=0.01), (
1651:  f"Expected {expected_cost}, got {result.total_cost}"
1652:  )
1653:  E       AssertionError: Expected 41035199.2, got 41035193.66664
1654:  E       assert 41035193.66664 == 41035199.2 ± 1.0e-02
1655:  E         
1656:  E         comparison failed
1657:  E         Obtained: 41035193.66664
1658:  E         Expected: 41035199.2 ± 1.0e-02
1659:  tests/test_calculator.py:120: AssertionError
1660:  ____ TestCalculatorFunctionCoverage.test_calculate_with_arm64_architecture _____
...

1662:  def test_calculate_with_arm64_architecture(self):
1663:  """Test calculate function with ARM64 architecture."""
1664:  result = calculate(
1665:  region="us-east-1",
1666:  architecture="arm64",
1667:  number_of_requests=1000,
1668:  request_unit="per hour",
1669:  duration_of_each_request_in_ms=100,
1670:  memory=512,
1671:  memory_unit="MB",
1672:  ephemeral_storage=512,
1673:  storage_unit="MB",
1674:  )
1675:  assert isinstance(result, CalculationResult)
1676:  >       assert result.total_cost > 0
1677:  E       AssertionError: assert 0.0 > 0
1678:  E        +  where 0.0 = CalculationResult(total_cost=0.0, calculation_steps=['\nUnit conversions:', 'Number of requests: 1000 per hour * (730 hours in a month) = 730000 per month', 'Amount of memory allocated: 512 MB * 0.0009765625 GB in MB = 0.5 GB', 'Amount of ephemeral storage allocated: 512 MB * 0.0009765625 GB in MB = 0.5 GB', '\nPricing calculations:', '730000 requests x 100 ms x 0.001 ms to sec conversion factor = 73000.0 total compute (seconds)', '0.5 GB x 73000.0 seconds = 36500.0 total compute (GB-s)', '36500.0 GB-s - 400000 free tier GB-s = 0.0 billable GB-s', 'Tiered price for: 0.0 GB-s', '0.0 GB-s x 1.33334e-05 USD = 0.0 USD', 'Total tier cost: 0.0 USD (Monthly compute charges)', 'Monthly compute charges: 0.0 USD\n', '730000 requests - 1000000 free tier requests = 0 monthly billable requests', 'Monthly request charges: 0.0 USD\n', '0.5 GB - 0.5 GB (no additional charges) = 0.0 GB (billable ephemeral storage)', 'Monthly ephemeral storage charges: 0.0 USD\n', '0.0 USD + 0.0 USD + 0.0 USD = 0.0 USD\n', 'Lambda cost (monthly): 0.0 USD']).total_cost
1679:  tests/test_calculator_coverage.py:166: AssertionError
1680:  ___________________ test_calculate_dynamic[params0-81827.82] ___________________
1681:  params = {'architecture': 'x86', 'duration_of_each_request_in_ms': 100, 'ephemeral_storage': 1024, 'memory': 1024, ...}
1682:  expected_cost = 81827.82
1683:  def test_calculate_dynamic(params, expected_cost):
1684:  result = calculate(**params)
1685:  >       assert result.total_cost == approx(expected_cost, abs=0.1), (
1686:  f"Expected {expected_cost}, got {result.total_cost}"
1687:  )
1688:  E       AssertionError: Expected 81827.82, got 81820.95032
1689:  E       assert 81820.95032 == 81827.82 ± 1.0e-01
1690:  E         
1691:  E         comparison failed
1692:  E         Obtained: 81820.95032
1693:  E         Expected: 81827.82 ± 1.0e-01
1694:  tests/test_pytest_generate_tests_sample_code.py:72: AssertionError
1695:  ____________________ test_calculate_dynamic[params1-104.5] _____________________
1696:  params = {'architecture': 'x86', 'duration_of_each_request_in_ms': 200, 'ephemeral_storage': 1280, 'memory': 2048, ...}
1697:  expected_cost = 104.5
1698:  def test_calculate_dynamic(params, expected_cost):
1699:  result = calculate(**params)
1700:  >       assert result.total_cost == approx(expected_cost, abs=0.1), (
1701:  f"Expected {expected_cost}, got {result.total_cost}"
1702:  )
1703:  E       AssertionError: Expected 104.5, got 97.70505729135
1704:  E       assert 97.70505729135 == 104.5 ± 1.0e-01
1705:  E         
1706:  E         comparison failed
1707:  E         Obtained: 97.70505729135
1708:  E         Expected: 104.5 ± 1.0e-01
1709:  tests/test_pytest_generate_tests_sample_code.py:72: AssertionError
1710:  ____________________ test_calculate_dynamic[params2-25.51] _____________________
1711:  params = {'architecture': 'x86', 'duration_of_each_request_in_ms': 500, 'ephemeral_storage': 4096, 'memory': 1536, ...}
1712:  expected_cost = 25.51
1713:  def test_calculate_dynamic(params, expected_cost):
1714:  result = calculate(**params)
1715:  >       assert result.total_cost == approx(expected_cost, abs=0.1), (
1716:  f"Expected {expected_cost}, got {result.total_cost}"
1717:  )
1718:  E       AssertionError: Expected 25.51, got 18.695595
1719:  E       assert 18.695595 == 25.51 ± 1.0e-01
1720:  E         
1721:  E         comparison failed
1722:  E         Obtained: 18.695595
1723:  E         Expected: 25.51 ± 1.0e-01
1724:  tests/test_pytest_generate_tests_sample_code.py:72: AssertionError
1725:  ___________________ test_calculate_dynamic[params3-2072.06] ____________________
1726:  params = {'architecture': 'x86', 'duration_of_each_request_in_ms': 5, 'ephemeral_storage': 3900, 'memory': 1024, ...}
1727:  expected_cost = 2072.06
1728:  def test_calculate_dynamic(params, expected_cost):
1729:  result = calculate(**params)
1730:  >       assert result.total_cost == approx(expected_cost, abs=0.1), (
1731:  f"Expected {expected_cost}, got {result.total_cost}"
1732:  )
1733:  E       AssertionError: Expected 2072.06, got 2065.1994674609373
1734:  E       assert 2065.1994674609373 == 2072.06 ± 1.0e-01
1735:  E         
1736:  E         comparison failed
1737:  E         Obtained: 2065.1994674609373
1738:  E         Expected: 2072.06 ± 1.0e-01
1739:  tests/test_pytest_generate_tests_sample_code.py:72: AssertionError
1740:  ================================ tests coverage ================================
1741:  _______________ coverage: platform linux, python 3.13.8-final-0 ________________
1742:  Name                                              Stmts   Miss Branch BrPart  Cover   Missing
1743:  ---------------------------------------------------------------------------------------------
1744:  src/aws_lambda_calculator/__init__.py                 2      0      0      0   100%
1745:  src/aws_lambda_calculator/calculator.py             165      1     42      5    97%   169->178, 216->227, 245->255, 371, 377->380
1746:  src/aws_lambda_calculator/models.py                  31      0     16      2    96%   58->63, 68->74
1747:  tests/__init__.py                                     0      0      0      0   100%
1748:  tests/test_calculator.py                              9      2      0      0    78%   124-125
1749:  tests/test_calculator_coverage.py                   100      0      0      0   100%
1750:  tests/test_models.py                                141      0      0      0   100%
1751:  tests/test_pytest_generate_tests_sample_code.py      10      1      2      1    83%   66->exit, 75
1752:  ---------------------------------------------------------------------------------------------
1753:  TOTAL                                               458      4     60      8    98%
1754:  Coverage XML written to file coverage.xml
1755:  =========================== short test summary info ============================
1756:  FAILED tests/test_calculator.py::test_calculate[us-east-1-x86-1000000-per day-100-1024-MB-512-MB-56.77] - AssertionError: Expected 56.77, got 49.911197922220005
1757:  assert 49.911197922220005 == 56.77 ± 1.0e-02
1758:  comparison failed
1759:  Obtained: 49.911197922220005
1760:  Expected: 56.77 ± 1.0e-02
1761:  FAILED tests/test_calculator.py::test_calculate[us-east-1-x86-500000-per month-200-2048-MB-512-MB-3.43] - AssertionError: Expected 3.43, got 0.0
1762:  assert 0.0 == 3.43 ± 1.0e-02
1763:  comparison failed
1764:  Obtained: 0.0
1765:  Expected: 3.43 ± 1.0e-02
1766:  FAILED tests/test_calculator.py::test_calculate[us-east-1-x86-2000000-per minute-500-1536-MB-512-MB-928523.58] - AssertionError: Expected 928523.58, got 928518.0466400001
1767:  assert 928518.0466400001 == 928523.58 ± 1.0e-02
1768:  comparison failed
1769:  Obtained: 928518.0466400001
1770:  Expected: 928523.58 ± 1.0e-02
1771:  FAILED tests/test_calculator.py::test_calculate[us-east-1-x86-50000000-per hour-1000-2048-MB-512-MB-1015637.4] - AssertionError: Expected 1015637.4, got 1015631.8666400001
1772:  assert 1015631.8666400001 == 1015637.4 ± 1.0e-02
1773:  comparison failed
1774:  Obtained: 1015631.8666400001
1775:  Expected: 1015637.4 ± 1.0e-02
1776:  FAILED tests/test_calculator.py::test_calculate[us-east-1-x86-10000000-per minute-1-1024-MB-512-MB-94900.01] - AssertionError: Expected 94900.01, got 94893.14792
1777:  assert 94893.14792 == 94900.01 ± 1.0e-02
1778:  comparison failed
1779:  Obtained: 94893.14792
1780:  Expected: 94900.01 ± 1.0e-02
1781:  FAILED tests/test_calculator.py::test_calculate[us-east-1-x86-50-per hour-100-1024-MB-5120-MB-0.07] - AssertionError: Expected 0.07, got 0.0005075324999999999
1782:  assert 0.0005075324999999999 == 0.07 ± 1.0e-02
1783:  comparison failed
1784:  Obtained: 0.0005075324999999999
1785:  Expected: 0.07 ± 1.0e-02
1786:  FAILED tests/test_calculator.py::test_calculate[us-east-1-x86-1000000-per day-100-8192-MB-512-MB-411.64] - AssertionError: Expected 411.64, got 404.77301097776
1787:  assert 404.77301097776 == 411.64 ± 1.0e-02
1788:  comparison failed
1789:  Obtained: 404.77301097776
1790:  Expected: 411.64 ± 1.0e-02
1791:  FAILED tests/test_calculator.py::test_calculate[us-east-1-x86-5000000-million per month-300-2048-MB-512-MB-41035199.2] - AssertionError: Expected 41035199.2, got 41035193.66664
1792:  assert 41035193.66664 == 41035199.2 ± 1.0e-02
1793:  comparison failed
1794:  Obtained: 41035193.66664
1795:  Expected: 41035199.2 ± 1.0e-02
1796:  FAILED tests/test_calculator_coverage.py::TestCalculatorFunctionCoverage::test_calculate_with_arm64_architecture - AssertionError: assert 0.0 > 0
1797:  +  where 0.0 = CalculationResult(total_cost=0.0, calculation_steps=['\nUnit conversions:', 'Number of requests: 1000 per hour * (730 hours in a month) = 730000 per month', 'Amount of memory allocated: 512 MB * 0.0009765625 GB in MB = 0.5 GB', 'Amount of ephemeral storage allocated: 512 MB * 0.0009765625 GB in MB = 0.5 GB', '\nPricing calculations:', '730000 requests x 100 ms x 0.001 ms to sec conversion factor = 73000.0 total compute (seconds)', '0.5 GB x 73000.0 seconds = 36500.0 total compute (GB-s)', '36500.0 GB-s - 400000 free tier GB-s = 0.0 billable GB-s', 'Tiered price for: 0.0 GB-s', '0.0 GB-s x 1.33334e-05 USD = 0.0 USD', 'Total tier cost: 0.0 USD (Monthly compute charges)', 'Monthly compute charges: 0.0 USD\n', '730000 requests - 1000000 free tier requests = 0 monthly billable requests', 'Monthly request charges: 0.0 USD\n', '0.5 GB - 0.5 GB (no additional charges) = 0.0 GB (billable ephemeral storage)', 'Monthly ephemeral storage charges: 0.0 USD\n', '0.0 USD + 0.0 USD + 0.0 USD = 0.0 USD\n', 'Lambda cost (monthly): 0.0 USD']).total_cost
1798:  FAILED tests/test_pytest_generate_tests_sample_code.py::test_calculate_dynamic[params0-81827.82] - AssertionError: Expected 81827.82, got 81820.95032
1799:  assert 81820.95032 == 81827.82 ± 1.0e-01
1800:  comparison failed
1801:  Obtained: 81820.95032
1802:  Expected: 81827.82 ± 1.0e-01
1803:  FAILED tests/test_pytest_generate_tests_sample_code.py::test_calculate_dynamic[params1-104.5] - AssertionError: Expected 104.5, got 97.70505729135
1804:  assert 97.70505729135 == 104.5 ± 1.0e-01
1805:  comparison failed
1806:  Obtained: 97.70505729135
1807:  Expected: 104.5 ± 1.0e-01
1808:  FAILED tests/test_pytest_generate_tests_sample_code.py::test_calculate_dynamic[params2-25.51] - AssertionError: Expected 25.51, got 18.695595
1809:  assert 18.695595 == 25.51 ± 1.0e-01
1810:  comparison failed
1811:  Obtained: 18.695595
1812:  Expected: 25.51 ± 1.0e-01
1813:  FAILED tests/test_pytest_generate_tests_sample_code.py::test_calculate_dynamic[params3-2072.06] - AssertionError: Expected 2072.06, got 2065.1994674609373
1814:  assert 2065.1994674609373 == 2072.06 ± 1.0e-01
1815:  comparison failed
1816:  Obtained: 2065.1994674609373
1817:  Expected: 2072.06 ± 1.0e-01
1818:  ======================== 13 failed, 50 passed in 0.94s =========================
1819:  ##[error]Process completed with exit code 1.
1820:  Post job cleanup.

@zMynxx zMynxx self-assigned this Oct 18, 2025
@codiumai-pr-agent-free
Copy link
Contributor

codiumai-pr-agent-free bot commented Oct 18, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix incorrect test case logic

Fix an incorrect expected value in the "per day" unit conversion test case and
adjust the assertion logic to correctly handle cases that do not produce
conversion steps.

tests/test_coverage_gaps.py [155-170]

 def test_all_request_units(self):
     """Test all request unit conversions to improve coverage."""
     test_cases = [
-        ("per second", 1, 60 * 60 * 730),
-        ("per minute", 1, 60 * 730),  
-        ("per hour", 1, 730),
-        ("per day", 1, 730 / 24),
+        ("per second", 1, 60 * 60 * 24 * 30.4167),
+        ("per minute", 1, 60 * 24 * 30.4167),
+        ("per hour", 1, 24 * 30.4167),
+        ("per day", 1, 30.4167),
         ("per month", 1000, 1000),
         ("million per month", 1, 1000000),
     ]
-    
+
     for unit, input_requests, expected_monthly in test_cases:
         steps = []
         result = unit_conversion_requests(input_requests, unit, steps)
         assert result == int(expected_monthly)
-        assert len(steps) > 0  # Should have conversion step
+        if unit not in ["per month", "million per month"]:
+            assert len(steps) > 0  # Should have conversion step
+        else:
+            # million per month has a conversion step, per month does not
+            if unit == "million per month":
+                assert len(steps) > 0
+            else:
+                assert len(steps) == 0
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a bug in a test case where the expected value is miscalculated, and another bug where an assertion would incorrectly fail, ensuring test accuracy.

Medium
High-level
Centralize hardcoded free tier constants

The free tier values for requests and compute are hardcoded in both the Python
backend and the React frontend. To improve maintainability, these values should
be centralized in a single backend configuration.

Examples:

aws-lambda-calculator/src/aws_lambda_calculator/calculator.py [217]
        free_compute_gb_sec = 400_000  # 400,000 GB-seconds per month
site/app/routes/demo.tsx [216]
                <p>The Lambda free tier includes 1M free requests per month and 400,000 GB-seconds of compute time per month.</p>

Solution Walkthrough:

Before:

# In aws_lambda_calculator/calculator.py
def calc_monthly_compute_charges(...):
    if include_free_tier:
        free_compute_gb_sec = 400_000  # Hardcoded value
        ...

def calc_monthly_request_charges(...):
    if include_free_tier:
        free_requests = 1_000_000  # Hardcoded value
        ...

# In site/app/routes/demo.tsx
<p>The Lambda free tier includes 1M free requests per month and 400,000 GB-seconds of compute time per month.</p>

After:

# In a new config file, e.g., aws_lambda_calculator/constants.py
FREE_TIER_REQUESTS = 1_000_000
FREE_TIER_COMPUTE_GB_SEC = 400_000

# In aws_lambda_calculator/calculator.py
from . import constants
def calc_monthly_compute_charges(...):
    if include_free_tier:
        free_compute_gb_sec = constants.FREE_TIER_COMPUTE_GB_SEC
        ...
def calc_monthly_request_charges(...):
    if include_free_tier:
        free_requests = constants.FREE_TIER_REQUESTS
        ...

# In site/app/routes/demo.tsx (values fetched from API)
<p>The Lambda free tier includes {freeTier.requests} free requests per month and {freeTier.compute} GB-seconds of compute time per month.</p>
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that free tier constants are hardcoded in both the backend (calculator.py) and frontend (demo.tsx), which is a valid maintainability concern that could lead to future inconsistencies.

Medium
General
Simplify conditional rendering logic

Simplify the conditional check for formData.include_free_tier by converting it
to a string and comparing it to 'true'.

site/app/routes/demo.tsx [203-221]

-{(formData.include_free_tier === true || formData.include_free_tier === 'true') && (
+{String(formData.include_free_tier) === 'true' && (
   <div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-8 dark:bg-blue-900/20 dark:border-blue-700">
     <div className="flex items-start">
       <div className="flex-shrink-0">
         <svg className="h-5 w-5 text-blue-400" fill="currentColor" viewBox="0 0 20 20">
           <path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" />
         </svg>
       </div>
       <div className="ml-3">
         <h3 className="text-sm font-medium text-blue-800 dark:text-blue-300">
           Free Tier
         </h3>
         <div className="mt-2 text-sm text-blue-700 dark:text-blue-400">
           <p>The Lambda free tier includes 1M free requests per month and 400,000 GB-seconds of compute time per month.</p>
         </div>
       </div>
     </div>
   </div>
 )}
  • Apply / Chat
Suggestion importance[1-10]: 2

__

Why: The suggestion offers a minor simplification of a boolean check, which is a stylistic change with negligible impact on functionality or readability.

Low
  • Update

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Introduce AWS Lambda Free Tier support across the calculator and UI, and improve test coverage for conversions, tiered pricing, and edge cases.

  • Add include_free_tier parameter and apply free tier logic for requests and compute GB-seconds.
  • Enhance UI to surface Free Tier information and add extensive tests for free tier scenarios and previously uncovered branches.
  • Minor test updates to disable free tier where exact pricing is expected.

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/test_free_tier.py New tests validating free tier behavior for requests and compute, including edge cases and step content.
tests/test_edge_cases.py Ensures long-duration test ignores free tier to validate actual pricing.
tests/test_coverage_gaps.py Adds coverage for unit conversions, tiered cost edge cases, overflow pricing, and invalid inputs.
site/app/routes/demo.tsx Displays a banner when free tier is enabled.
aws-lambda-calculator/src/aws_lambda_calculator/models.py Adds include_free_tier to CalculationRequest for validation and schema.
aws-lambda-calculator/src/aws_lambda_calculator/calculator.py Implements free tier logic for compute and requests; threads include_free_tier through calculate and helpers.
Comments suppressed due to low confidence (1)

aws-lambda-calculator/src/aws_lambda_calculator/calculator.py:197

  • The docstring is outdated: the function now accepts include_free_tier and returns a tuple (total_compute_gb_sec, monthly_compute_charges), but @return documents only a single value and include_free_tier is undocumented. Update the docstring to include the new parameter and correct the return description.
) -> tuple[float, float]:
    """
    @brief Calculate the monthly compute charges based on requests per month, duration of each request in ms, and memory in GB.
    @param requests_per_month: The number of requests per month.
    @param duration_of_each_request_in_ms: The duration of each request in milliseconds.
    @param memory_in_gb: The amount of memory allocated in GB.
    @return: The monthly compute charges.
    """

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@zMynxx zMynxx merged commit 6ad8570 into main Oct 18, 2025
12 of 13 checks passed
@zMynxx zMynxx deleted the feat/free-tier branch October 18, 2025 01:17
@sonarqubecloud
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants