Skip to content

Conversation

akshay-nanda-sneo
Copy link
Contributor

@akshay-nanda-sneo akshay-nanda-sneo commented Sep 27, 2025

PR Type

Enhancement


Description

  • Add new /anime/quote endpoint for random anime quotes

  • Integrate with Animechan API with proper error handling

  • Add comprehensive test coverage for API integration

  • Include Pydantic models for response validation


Diagram Walkthrough

flowchart LR
  A["Client Request"] --> B["/anime/quote endpoint"]
  B --> C["Animechan API"]
  C --> D["Response Validation"]
  D --> E["JSON Response"]
Loading

File Walkthrough

Relevant files
Enhancement
main.py
Add anime quote endpoint with API integration                       

src/ssdlc_demo/main.py

  • Add new /anime/quote GET endpoint
  • Import requests and HTTPException modules
  • Define Pydantic models for anime API response structure
  • Implement error handling for network, JSON, and validation errors
+68/-1   
Tests
test_anime.py
Add test coverage for anime API endpoint                                 

tests/test_anime.py

  • Create comprehensive test suite for anime endpoint
  • Test successful API response scenario
  • Test upstream error handling (500 status)
  • Test invalid JSON response handling
+72/-0   

Copy link

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Strict Schema Assumption

The Pydantic models assume a specific Animechan response structure (including nested ids and status field). If the upstream API shape differs, valid responses may be rejected; consider aligning the schema to the actual API or relaxing/transforming fields.

class AnimeInfo(BaseModel):
    id: int
    name: str
    altName: str | None = None


class CharacterInfo(BaseModel):
    id: int
    name: str


class AnimechanData(BaseModel):
    content: str
    anime: AnimeInfo
    character: CharacterInfo


class AnimechanResponse(BaseModel):
    status: str
    data: AnimechanData
Exception Granularity

Catching broad exceptions during validation may mask actionable errors; consider catching Pydantic ValidationError explicitly and logging details to aid troubleshooting.

# Validate and coerce to our response model
try:
    validated = AnimechanResponse.model_validate(payload)
except Exception as exc:
    raise HTTPException(
        status_code=502,
        detail="Unexpected Animechan schema",
    ) from exc
Missing Timeout/Network Test

Tests cover non-200 and invalid JSON but do not simulate network/timeout exceptions; adding a test for requests exceptions would strengthen reliability claims.

from fastapi.testclient import TestClient

from ssdlc_demo.main import app


def test_anime_quote_success(monkeypatch) -> None:
    class DummyResponse:
        status_code = 200

        def json(self):
            return {
                "status": "success",
                "data": {
                    "content": "Test quote",
                    "anime": {"id": 1, "name": "Naruto", "altName": "NARUTO"},
                    "character": {"id": 2, "name": "Naruto Uzumaki"},
                },
            }

    def fake_get(url, timeout=5):  # type: ignore[no-untyped-def]
        assert url == "https://api.animechan.io/v1/quotes/random"
        assert timeout == 5
        return DummyResponse()

    import ssdlc_demo.main as main_mod

    monkeypatch.setattr(main_mod.requests, "get", fake_get)

    client = TestClient(app)
    res = client.get("/anime/quote")
    assert res.status_code == 200
    body = res.json()
    assert body["status"] == "success"
    assert body["data"]["content"] == "Test quote"


def test_anime_quote_upstream_error(monkeypatch) -> None:
    class DummyResponse:
        status_code = 500

        def json(self):
            return {}

    def fake_get(url, timeout=5):  # type: ignore[no-untyped-def]
        return DummyResponse()

    import ssdlc_demo.main as main_mod

    monkeypatch.setattr(main_mod.requests, "get", fake_get)

    client = TestClient(app)
    res = client.get("/anime/quote")
    assert res.status_code == 502


def test_anime_quote_invalid_json(monkeypatch) -> None:
    class DummyResponse:
        status_code = 200

        def json(self):  # type: ignore[no-untyped-def]
            raise ValueError("invalid json")

    def fake_get(url, timeout=5):  # type: ignore[no-untyped-def]
        return DummyResponse()

    import ssdlc_demo.main as main_mod

    monkeypatch.setattr(main_mod.requests, "get", fake_get)

    client = TestClient(app)
    res = client.get("/anime/quote")
    assert res.status_code == 502

Copy link

CI Feedback 🧐

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

Action: iac-scan

Failed stage: Checkov IaC scan [❌]

Failed test name: ""

Failure summary:

The action failed because Checkov security/compliance scan found multiple failed Terraform checks in
the infra directory and exited with a non-zero status. Key failures include:
- CKV_AWS_136 and
CKV_AWS_51 on aws_ecr_repository.this (file /main.tf:21-25) for missing KMS encryption and mutable
image tags.
- CKV_AWS_130 on aws_subnet.public_a and aws_subnet.public_b (files /main.tf:35-40 and
/main.tf:42-47) for assigning public IPs by default.
- CKV_AWS_260, CKV_AWS_23, and CKV_AWS_382 on
aws_security_group.alb (file /main.tf:69-87) for open ingress 0.0.0.0/0:80, missing descriptions,
and open egress.
- CKV_AWS_150, CKV_AWS_131, CKV_AWS_91, CKV2_AWS_28, and CKV2_AWS_20 on aws_lb.this
(file /main.tf:89-95) for missing deletion protection, not dropping HTTP headers, missing access
logging, no WAF, and not redirecting HTTP to HTTPS.
- CKV_AWS_2 and CKV_AWS_103 on
aws_lb_listener.http (file /main.tf:112-121) for using HTTP and not enforcing TLS 1.2.
- CKV_AWS_65
on aws_ecs_cluster.this (file /main.tf:123-125) for container insights disabled.
- CKV_AWS_23 and
CKV_AWS_382 on aws_security_group.service (file /main.tf:144-161) for missing descriptions and open
egress.
- CKV_AWS_336 on aws_ecs_task_definition.this (file /main.tf:163-179) for not using
read-only root filesystem.
- CKV_AWS_333 on aws_ecs_service.this (file /main.tf:181-198) for
assigning public IPs.
- CKV_AWS_378 on aws_lb_target_group.this (file /main.tf:97-110) for using
HTTP protocol.
- CKV2_AWS_12 and CKV2_AWS_11 on aws_vpc.this (file /main.tf:27-29) for default SG
not restricting all traffic and VPC flow logs disabled.
Passed checks: 31, Failed checks: 22. The
presence of these failures caused the CI step to error out.

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

162:  Turn off this advice by setting config variable advice.detachedHead to false
163:  HEAD is now at 91edbe3 Merge 06cc0ad3b5505dd3575b14247525944ef8322125 into 58106c63981e5a103cccd084002d6995503ca4c8
164:  ##[endgroup]
165:  [command]/usr/bin/git log -1 --format=%H
166:  91edbe311d1800b4e37ebef4779b69e629798e24
167:  ##[group]Run bridgecrewio/checkov-action@v12
168:  with:
169:  directory: infra
170:  quiet: true
171:  framework: terraform
172:  download_external_modules: true
173:  output_format: sarif
174:  log_level: WARNING
175:  container_user: 0
176:  ##[endgroup]
177:  ##[command]/usr/bin/docker run --name ghcriobridgecrewiocheckov32471_ae867a --label 97299f --workdir /github/workspace --rm -e "INPUT_DIRECTORY" -e "INPUT_QUIET" -e "INPUT_FRAMEWORK" -e "INPUT_DOWNLOAD_EXTERNAL_MODULES" -e "INPUT_FILE" -e "INPUT_CHECK" -e "INPUT_SKIP_CHECK" -e "INPUT_COMPACT" -e "INPUT_API-KEY" -e "INPUT_OUTPUT_BC_IDS" -e "INPUT_USE_ENFORCEMENT_RULES" -e "INPUT_SKIP_RESULTS_UPLOAD" -e "INPUT_SOFT_FAIL" -e "INPUT_SKIP_FRAMEWORK" -e "INPUT_EXTERNAL_CHECKS_DIRS" -e "INPUT_EXTERNAL_CHECKS_REPOS" -e "INPUT_OUTPUT_FORMAT" -e "INPUT_OUTPUT_FILE_PATH" -e "INPUT_ENABLE_SECRETS_SCAN_ALL_FILES" -e "INPUT_LOG_LEVEL" -e "INPUT_CONFIG_FILE" -e "INPUT_BASELINE" -e "INPUT_SOFT_FAIL_ON" -e "INPUT_HARD_FAIL_ON" -e "INPUT_CONTAINER_USER" -e "INPUT_DOCKER_IMAGE" -e "INPUT_DOCKERFILE_PATH" -e "INPUT_VAR_FILE" -e "INPUT_GITHUB_PAT" -e "INPUT_TFC_TOKEN" -e "INPUT_TF_REGISTRY_TOKEN" -e "INPUT_CKV_VALIDATE_SECRETS" -e "INPUT_VCS_BASE_URL" -e "INPUT_VCS_USERNAME" -e "INPUT_VCS_TOKEN" -e "INPUT_BITBUCKET_TOKEN" -e "INPUT_BITBUCKET_APP_PASSWORD" -e "INPUT_BITBUCKET_USERNAME" -e "INPUT_REPO_ROOT_FOR_PLAN_ENRICHMENT" -e "INPUT_DEEP_ANALYSIS" -e "INPUT_POLICY_METADATA_FILTER" -e "INPUT_POLICY_METADATA_FILTER_EXCEPTION" -e "INPUT_SKIP_PATH" -e "INPUT_SKIP_CVE_PACKAGE" -e "INPUT_SKIP_DOWNLOAD" -e "INPUT_PRISMA-API-URL" -e "API_KEY_VARIABLE" -e "GITHUB_PAT" -e "TFC_TOKEN" -e "TF_REGISTRY_TOKEN" -e "VCS_USERNAME" -e "VCS_BASE_URL" -e "VCS_TOKEN" -e "BITBUCKET_TOKEN" -e "BITBUCKET_USERNAME" -e "BITBUCKET_APP_PASSWORD" -e "PRISMA_API_URL" -e "CKV_VALIDATE_SECRETS" -e "HOME" -e "GITHUB_JOB" -e "GITHUB_REF" -e "GITHUB_SHA" -e "GITHUB_REPOSITORY" -e "GITHUB_REPOSITORY_OWNER" -e "GITHUB_REPOSITORY_OWNER_ID" -e "GITHUB_RUN_ID" -e "GITHUB_RUN_NUMBER" -e "GITHUB_RETENTION_DAYS" -e "GITHUB_RUN_ATTEMPT" -e "GITHUB_ACTOR_ID" -e "GITHUB_ACTOR" -e "GITHUB_WORKFLOW" -e "GITHUB_HEAD_REF" -e "GITHUB_BASE_REF" -e "GITHUB_EVENT_NAME" -e "GITHUB_SERVER_URL" -e "GITHUB_API_URL" -e "GITHUB_GRAPHQL_URL" -e "GITHUB_REF_NAME" -e "GITHUB_REF_PROTECTED" -e "GITHUB_REF_TYPE" -e "GITHUB_WORKFLOW_REF" -e "GITHUB_WORKFLOW_SHA" -e "GITHUB_REPOSITORY_ID" -e "GITHUB_TRIGGERING_ACTOR" -e "GITHUB_WORKSPACE" -e "GITHUB_ACTION" -e "GITHUB_EVENT_PATH" -e "GITHUB_ACTION_REPOSITORY" -e "GITHUB_ACTION_REF" -e "GITHUB_PATH" -e "GITHUB_ENV" -e "GITHUB_STEP_SUMMARY" -e "GITHUB_STATE" -e "GITHUB_OUTPUT" -e "RUNNER_OS" -e "RUNNER_ARCH" -e "RUNNER_NAME" -e "RUNNER_ENVIRONMENT" -e "RUNNER_TOOL_CACHE" -e "RUNNER_TEMP" -e "RUNNER_WORKSPACE" -e "ACTIONS_RUNTIME_URL" -e "ACTIONS_RUNTIME_TOKEN" -e "ACTIONS_CACHE_URL" -e "ACTIONS_RESULTS_URL" -e GITHUB_ACTIONS=true -e CI=true -v "/var/run/docker.sock":"/var/run/docker.sock" -v "/home/runner/work/_temp/_github_home":"/github/home" -v "/home/runner/work/_temp/_github_workflow":"/github/workflow" -v "/home/runner/work/_temp/_runner_file_commands":"/github/file_commands" -v "/home/runner/work/ssdlc-demo/ssdlc-demo":"/github/workspace" ghcr.io/bridgecrewio/checkov:3.2.471  "" "infra" "" "" "" "true" "" "" "" "" "terraform" "" "" "" "sarif" "" "true" "" "WARNING" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "--user 0"
178:  BC_FROM_BRANCH=feat/anime_api_integration
...

183:  BC_COMMIT_URL=https://github.com/StatusNeo/ssdlc-demo/commit/91edbe311d1800b4e37ebef4779b69e629798e24
184:  BC_AUTHOR_NAME=akshay-nanda-sneo
185:  BC_AUTHOR_URL=https://github.com/akshay-nanda-sneo
186:  BC_RUN_ID=32
187:  BC_RUN_URL=https://github.com/StatusNeo/ssdlc-demo/actions/runs/18055936794
188:  BC_REPOSITORY_URL=https://github.com/StatusNeo/ssdlc-demo
189:  running checkov on directory: infra
190:  checkov -d infra    --quiet         --output sarif   --download-external-modules true    --framework terraform         
191:  _               _
192:  ___| |__   ___  ___| | _______   __
193:  / __| '_ \ / _ \/ __| |/ / _ \ \ / /
194:  | (__| | | |  __/ (__|   < (_) \ V /
195:  \___|_| |_|\___|\___|_|\_\___/ \_/
196:  By Prisma Cloud | version: 3.2.471 
197:  terraform scan results:
198:  Passed checks: 31, Failed checks: 22, Skipped checks: 0
199:  Check: CKV_AWS_136: "Ensure that ECR repositories are encrypted using KMS"
200:  FAILED for resource: aws_ecr_repository.this
201:  ##[error]	File: /main.tf:21-25
202:  Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-general-policies/ensure-that-ecr-repositories-are-encrypted
203:  21 | resource "aws_ecr_repository" "this" {
204:  22 |   name                 = local.name
205:  23 |   image_tag_mutability = "MUTABLE"
206:  24 |   image_scanning_configuration { scan_on_push = true }
207:  25 | }
208:  Check: CKV_AWS_51: "Ensure ECR Image Tags are immutable"
209:  FAILED for resource: aws_ecr_repository.this
210:  ##[error]	File: /main.tf:21-25
211:  Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-general-policies/bc-aws-general-24
212:  21 | resource "aws_ecr_repository" "this" {
213:  22 |   name                 = local.name
214:  23 |   image_tag_mutability = "MUTABLE"
215:  24 |   image_scanning_configuration { scan_on_push = true }
216:  25 | }
217:  Check: CKV_AWS_130: "Ensure VPC subnets do not assign public IP by default"
218:  FAILED for resource: aws_subnet.public_a
219:  ##[error]	File: /main.tf:35-40
220:  Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-networking-policies/ensure-vpc-subnets-do-not-assign-public-ip-by-default
221:  35 | resource "aws_subnet" "public_a" {
222:  36 |   vpc_id                  = aws_vpc.this.id
223:  37 |   cidr_block              = "10.0.1.0/24"
224:  38 |   map_public_ip_on_launch = true
225:  39 |   availability_zone       = data.aws_availability_zones.available.names[0]
226:  40 | }
227:  Check: CKV_AWS_130: "Ensure VPC subnets do not assign public IP by default"
228:  FAILED for resource: aws_subnet.public_b
229:  ##[error]	File: /main.tf:42-47
230:  Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-networking-policies/ensure-vpc-subnets-do-not-assign-public-ip-by-default
231:  42 | resource "aws_subnet" "public_b" {
232:  43 |   vpc_id                  = aws_vpc.this.id
233:  44 |   cidr_block              = "10.0.2.0/24"
234:  45 |   map_public_ip_on_launch = true
235:  46 |   availability_zone       = data.aws_availability_zones.available.names[1]
236:  47 | }
237:  Check: CKV_AWS_260: "Ensure no security groups allow ingress from 0.0.0.0:0 to port 80"
238:  FAILED for resource: aws_security_group.alb
239:  ##[error]	File: /main.tf:69-87
240:  Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-networking-policies/ensure-aws-security-groups-do-not-allow-ingress-from-00000-to-port-80
...

246:  74 |   ingress {
247:  75 |     from_port   = 80
248:  76 |     to_port     = 80
249:  77 |     protocol    = "tcp"
250:  78 |     cidr_blocks = ["0.0.0.0/0"]
251:  79 |   }
252:  80 | 
253:  81 |   egress {
254:  82 |     from_port   = 0
255:  83 |     to_port     = 0
256:  84 |     protocol    = "-1"
257:  85 |     cidr_blocks = ["0.0.0.0/0"]
258:  86 |   }
259:  87 | }
260:  Check: CKV_AWS_23: "Ensure every security group and rule has a description"
261:  FAILED for resource: aws_security_group.alb
262:  ##[error]	File: /main.tf:69-87
263:  Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-networking-policies/networking-31
...

269:  74 |   ingress {
270:  75 |     from_port   = 80
271:  76 |     to_port     = 80
272:  77 |     protocol    = "tcp"
273:  78 |     cidr_blocks = ["0.0.0.0/0"]
274:  79 |   }
275:  80 | 
276:  81 |   egress {
277:  82 |     from_port   = 0
278:  83 |     to_port     = 0
279:  84 |     protocol    = "-1"
280:  85 |     cidr_blocks = ["0.0.0.0/0"]
281:  86 |   }
282:  87 | }
283:  Check: CKV_AWS_382: "Ensure no security groups allow egress from 0.0.0.0:0 to port -1"
284:  FAILED for resource: aws_security_group.alb
285:  ##[error]	File: /main.tf:69-87
286:  Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-networking-policies/bc-aws-382
...

292:  74 |   ingress {
293:  75 |     from_port   = 80
294:  76 |     to_port     = 80
295:  77 |     protocol    = "tcp"
296:  78 |     cidr_blocks = ["0.0.0.0/0"]
297:  79 |   }
298:  80 | 
299:  81 |   egress {
300:  82 |     from_port   = 0
301:  83 |     to_port     = 0
302:  84 |     protocol    = "-1"
303:  85 |     cidr_blocks = ["0.0.0.0/0"]
304:  86 |   }
305:  87 | }
306:  Check: CKV_AWS_150: "Ensure that Load Balancer has deletion protection enabled"
307:  FAILED for resource: aws_lb.this
308:  ##[error]	File: /main.tf:89-95
309:  Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-general-policies/bc-aws-150
310:  89 | resource "aws_lb" "this" {
311:  90 |   name               = "${local.name}-alb"
312:  91 |   internal           = false
313:  92 |   load_balancer_type = "application"
314:  93 |   security_groups    = [aws_security_group.alb.id]
315:  94 |   subnets            = [aws_subnet.public_a.id, aws_subnet.public_b.id]
316:  95 | }
317:  Check: CKV_AWS_131: "Ensure that ALB drops HTTP headers"
318:  FAILED for resource: aws_lb.this
319:  ##[error]	File: /main.tf:89-95
320:  Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-networking-policies/ensure-that-alb-drops-http-headers
321:  89 | resource "aws_lb" "this" {
322:  90 |   name               = "${local.name}-alb"
323:  91 |   internal           = false
324:  92 |   load_balancer_type = "application"
325:  93 |   security_groups    = [aws_security_group.alb.id]
326:  94 |   subnets            = [aws_subnet.public_a.id, aws_subnet.public_b.id]
327:  95 | }
328:  Check: CKV_AWS_91: "Ensure the ELBv2 (Application/Network) has access logging enabled"
329:  FAILED for resource: aws_lb.this
330:  ##[error]	File: /main.tf:89-95
331:  Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-logging-policies/bc-aws-logging-22
332:  89 | resource "aws_lb" "this" {
333:  90 |   name               = "${local.name}-alb"
334:  91 |   internal           = false
335:  92 |   load_balancer_type = "application"
336:  93 |   security_groups    = [aws_security_group.alb.id]
337:  94 |   subnets            = [aws_subnet.public_a.id, aws_subnet.public_b.id]
338:  95 | }
339:  Check: CKV_AWS_2: "Ensure ALB protocol is HTTPS"
340:  FAILED for resource: aws_lb_listener.http
341:  ##[error]	File: /main.tf:112-121
342:  Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-networking-policies/networking-29
343:  112 | resource "aws_lb_listener" "http" {
344:  113 |   load_balancer_arn = aws_lb.this.arn
345:  114 |   port              = 80
346:  115 |   protocol          = "HTTP"
347:  116 | 
348:  117 |   default_action {
349:  118 |     type             = "forward"
350:  119 |     target_group_arn = aws_lb_target_group.this.arn
351:  120 |   }
352:  121 | }
353:  Check: CKV_AWS_65: "Ensure container insights are enabled on ECS cluster"
354:  FAILED for resource: aws_ecs_cluster.this
355:  ##[error]	File: /main.tf:123-125
356:  Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-logging-policies/bc-aws-logging-11
357:  123 | resource "aws_ecs_cluster" "this" {
358:  124 |   name = local.name
359:  125 | }
360:  Check: CKV_AWS_23: "Ensure every security group and rule has a description"
361:  FAILED for resource: aws_security_group.service
362:  ##[error]	File: /main.tf:144-161
363:  Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-networking-policies/networking-31
...

368:  148 |   ingress {
369:  149 |     from_port       = 8000
370:  150 |     to_port         = 8000
371:  151 |     protocol        = "tcp"
372:  152 |     security_groups = [aws_security_group.alb.id]
373:  153 |   }
374:  154 | 
375:  155 |   egress {
376:  156 |     from_port   = 0
377:  157 |     to_port     = 0
378:  158 |     protocol    = "-1"
379:  159 |     cidr_blocks = ["0.0.0.0/0"]
380:  160 |   }
381:  161 | }
382:  Check: CKV_AWS_382: "Ensure no security groups allow egress from 0.0.0.0:0 to port -1"
383:  FAILED for resource: aws_security_group.service
384:  ##[error]	File: /main.tf:144-161
385:  Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-networking-policies/bc-aws-382
...

390:  148 |   ingress {
391:  149 |     from_port       = 8000
392:  150 |     to_port         = 8000
393:  151 |     protocol        = "tcp"
394:  152 |     security_groups = [aws_security_group.alb.id]
395:  153 |   }
396:  154 | 
397:  155 |   egress {
398:  156 |     from_port   = 0
399:  157 |     to_port     = 0
400:  158 |     protocol    = "-1"
401:  159 |     cidr_blocks = ["0.0.0.0/0"]
402:  160 |   }
403:  161 | }
404:  Check: CKV_AWS_336: "Ensure ECS containers are limited to read-only access to root filesystems"
405:  FAILED for resource: aws_ecs_task_definition.this
406:  ##[error]	File: /main.tf:163-179
407:  Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-general-policies/bc-aws-336
...

411:  166 |   cpu                      = "256"
412:  167 |   memory                   = "512"
413:  168 |   requires_compatibilities = ["FARGATE"]
414:  169 |   execution_role_arn       = aws_iam_role.task_execution.arn
415:  170 |   container_definitions = jsonencode([
416:  171 |     {
417:  172 |       name  = local.name
418:  173 |       image = "${aws_ecr_repository.this.repository_url}:${var.image_tag}"
419:  174 |       portMappings = [{ containerPort = 8000, hostPort = 8000, protocol = "tcp" }]
420:  175 |       essential = true
421:  176 |       environment = [{ name = "PORT", value = "8000" }]
422:  177 |     }
423:  178 |   ])
424:  179 | }
425:  Check: CKV_AWS_333: "Ensure ECS services do not have public IP addresses assigned to them automatically"
426:  FAILED for resource: aws_ecs_service.this
427:  ##[error]	File: /main.tf:181-198
428:  Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-logging-policies/bc-aws-333
...

433:  185 |   desired_count   = 1
434:  186 |   launch_type     = "FARGATE"
435:  187 |   network_configuration {
436:  188 |     subnets          = [aws_subnet.public_a.id, aws_subnet.public_b.id]
437:  189 |     security_groups  = [aws_security_group.service.id]
438:  190 |     assign_public_ip = true
439:  191 |   }
440:  192 |   load_balancer {
441:  193 |     target_group_arn = aws_lb_target_group.this.arn
442:  194 |     container_name   = local.name
443:  195 |     container_port   = 8000
444:  196 |   }
445:  197 |   depends_on = [aws_lb_listener.http]
446:  198 | } 
447:  Check: CKV_AWS_378: "Ensure AWS Load Balancer doesn't use HTTP protocol"
448:  FAILED for resource: aws_lb_target_group.this
449:  ##[error]	File: /main.tf:97-110
450:  Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-networking-policies/bc-aws-378
451:  97  | resource "aws_lb_target_group" "this" {
452:  98  |   name     = "${local.name}-tg"
453:  99  |   port     = 8000
454:  100 |   protocol = "HTTP"
455:  101 |   vpc_id   = aws_vpc.this.id
456:  102 |   health_check {
457:  103 |     path                = "/health"
458:  104 |     healthy_threshold   = 2
459:  105 |     unhealthy_threshold = 3
460:  106 |     timeout             = 5
461:  107 |     interval            = 30
462:  108 |     matcher             = "200"
463:  109 |   }
464:  110 | }
465:  Check: CKV2_AWS_28: "Ensure public facing ALB are protected by WAF"
466:  FAILED for resource: aws_lb.this
467:  ##[error]	File: /main.tf:89-95
468:  Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-networking-policies/ensure-public-facing-alb-are-protected-by-waf
469:  89 | resource "aws_lb" "this" {
470:  90 |   name               = "${local.name}-alb"
471:  91 |   internal           = false
472:  92 |   load_balancer_type = "application"
473:  93 |   security_groups    = [aws_security_group.alb.id]
474:  94 |   subnets            = [aws_subnet.public_a.id, aws_subnet.public_b.id]
475:  95 | }
476:  Check: CKV2_AWS_12: "Ensure the default security group of every VPC restricts all traffic"
477:  FAILED for resource: aws_vpc.this
478:  ##[error]	File: /main.tf:27-29
479:  Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-networking-policies/networking-4
480:  27 | resource "aws_vpc" "this" {
481:  28 |   cidr_block = "10.0.0.0/16"
482:  29 | }
483:  Check: CKV2_AWS_11: "Ensure VPC flow logging is enabled in all VPCs"
484:  FAILED for resource: aws_vpc.this
485:  ##[error]	File: /main.tf:27-29
486:  Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-logging-policies/logging-9-enable-vpc-flow-logging
487:  27 | resource "aws_vpc" "this" {
488:  28 |   cidr_block = "10.0.0.0/16"
489:  29 | }
490:  Check: CKV2_AWS_20: "Ensure that ALB redirects HTTP requests into HTTPS ones"
491:  FAILED for resource: aws_lb.this
492:  ##[error]	File: /main.tf:89-95
493:  Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-networking-policies/ensure-that-alb-redirects-http-requests-into-https-ones
494:  89 | resource "aws_lb" "this" {
495:  90 |   name               = "${local.name}-alb"
496:  91 |   internal           = false
497:  92 |   load_balancer_type = "application"
498:  93 |   security_groups    = [aws_security_group.alb.id]
499:  94 |   subnets            = [aws_subnet.public_a.id, aws_subnet.public_b.id]
500:  95 | }
501:  Check: CKV_AWS_103: "Ensure that load balancer is using at least TLS 1.2"
502:  FAILED for resource: aws_lb_listener.http
503:  ##[error]	File: /main.tf:112-121
504:  Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-general-policies/bc-aws-general-43

Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Separate external API client logic

Abstract the Animechan API interaction logic out of the FastAPI route handler
and into a separate client function or class. This will decouple the web layer
from the external service integration.

Examples:

src/ssdlc_demo/main.py [53-94]
@app.get("/anime/quote", response_model=AnimechanResponse)
def get_random_anime_quote() -> AnimechanResponse:
    """Fetch a random anime quote from Animechan and return it.

    Docs: https://animechan.io/
    Endpoint used: https://api.animechan.io/v1/quotes/random
    """
    try:
        res = requests.get(
            "https://api.animechan.io/v1/quotes/random",

 ... (clipped 32 lines)

Solution Walkthrough:

Before:

# src/ssdlc_demo/main.py

@app.get("/anime/quote", response_model=AnimechanResponse)
def get_random_anime_quote() -> AnimechanResponse:
    try:
        res = requests.get("https://api.animechan.io/...", timeout=5)
    except requests.RequestException as exc:
        raise HTTPException(status_code=502, detail="...")

    if res.status_code != 200:
        raise HTTPException(status_code=502, detail="...")

    try:
        payload = res.json()
        validated = AnimechanResponse.model_validate(payload)
    except Exception as exc:
        raise HTTPException(status_code=502, detail="...")

    return validated

After:

# src/ssdlc_demo/clients/animechan.py

def get_random_quote() -> AnimechanData:
    # All logic for calling the API, handling errors,
    # and validating the response is moved here.
    # It raises custom exceptions on failure.
    try:
        res = requests.get("https://api.animechan.io/...", timeout=5)
        res.raise_for_status()
        payload = res.json()
        return AnimechanResponse.model_validate(payload).data
    except (requests.RequestException, ValueError, ValidationError) as e:
        raise ServiceError("Failed to fetch quote from Animechan") from e

# src/ssdlc_demo/main.py

@app.get("/anime/quote", response_model=AnimechanResponse)
def get_random_anime_quote() -> AnimechanResponse:
    try:
        quote_data = animechan_client.get_random_quote()
        return AnimechanResponse(status="success", data=quote_data)
    except animechan_client.ServiceError as e:
        raise HTTPException(status_code=502, detail=str(e))
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies tight coupling in the new endpoint and proposes a valid architectural improvement by separating the API client logic, which enhances maintainability and testability.

Medium
General
Assert error response body in test

In test_anime_quote_upstream_error, add an assertion to verify the content of
the JSON error response body, specifically checking the detail message.

tests/test_anime.py [37-53]

 def test_anime_quote_upstream_error(monkeypatch) -> None:
     class DummyResponse:
         status_code = 500
 
         def json(self):
             return {}
 
     def fake_get(url, timeout=5):  # type: ignore[no-untyped-def]
         return DummyResponse()
 
     import ssdlc_demo.main as main_mod
 
     monkeypatch.setattr(main_mod.requests, "get", fake_get)
 
     client = TestClient(app)
     res = client.get("/anime/quote")
     assert res.status_code == 502
+    assert res.json() == {"detail": "Animechan returned non-200 status"}
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly points out that the test should also assert the content of the error response body, making the test more comprehensive and ensuring the API contract is met.

Low
  • More

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.

1 participant