Repository: mimo-owl/mercari-build-training
Pull request #8: Completed Step6
Author: login: mimo-owl
Created: 2025-03-14T02:17:43Z | Merged: 2025-03-24T06:29:29Z

## What
<!--- Write the change being made with this pull request --->
6-0: Made a hello.py and hello_test.py and confirmed the test functionality
<img width="513" alt="Screenshot 2025-03-14 at 10 18 03" src="https://github.com/user-attachments/assets/ccaaf53b-16c9-4f5a-a8f0-c04b6b2242ad" />

6-1: read the document
6-2: Implemented test codes for 'say_hello()' function (main.py) in main_test.py
<img width="507" alt="Screenshot 2025-03-14 at 10 18 15" src="https://github.com/user-attachments/assets/76ad13ac-c7d4-455c-851d-5d7a9d8402af" />

6-3: Confirmed the main.py implementation functionality with e2e test (by uncommenting the indicated places in main_test.py).

## CHECKS :warning:

Please make sure you are aware of the following.

- [ ] **The changes in this PR doesn't have private information



Diff:
diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 000000000..10a893458
Binary files /dev/null and b/.DS_Store differ
diff --git a/README.md b/README.md
index 534aa2326..0bf01fa49 100644
--- a/README.md
+++ b/README.md
@@ -26,11 +26,11 @@ The following icons indicate pointers for
 - [x] **STEP1** Git ([JA](document/01-git.ja.md)/[EN](document/01-git.en.md))
 - [x] **STEP2** Setup environment ([JA](document/02-local-env.ja.md)
   /[EN](document/02-local-env.en.md))
-- [ ] **STEP3** Algorithms and Data Structures([JA](document/03-algorithm-and-data-structure.ja.md)/[EN](document/03-algorithm-and-data-structure.en.md))
-- [ ] **STEP4** Develop API ([JA](document/04-api.ja.md)
+- [x] **STEP3** Algorithms and Data Structures([JA](document/03-algorithm-and-data-structure.ja.md)/[EN](document/03-algorithm-and-data-structure.en.md))
+- [x] **STEP4** Develop API ([JA](document/04-api.ja.md)
   /[EN](document/04-api.en.md))
-- [ ] **STEP5** Database ([JA](document/05-database.ja.md)/[EN](document/05-database.en.md))
-- [ ] **STEP6** Writing Tests ([JA](document/06-testing.ja.md)/[EN](document/06-testing.en.md))
+- [x] **STEP5** Database ([JA](document/05-database.ja.md)/[EN](document/05-database.en.md))
+- [x] **STEP6** Writing Tests ([JA](document/06-testing.ja.md)/[EN](document/06-testing.en.md))
 - [ ] **STEP7** Docker ([JA](document/07-docker.ja.md)/[EN](document/07-docker.en.md))
 - [ ] **STEP8** Continuous Integration(CI) ([JA](document/08-ci.ja.md)
   /[EN](document/08-ci.en.md))
diff --git a/python/.gitignore b/python/.gitignore
index 60dd78e3f..a54ffc4c6 100644
--- a/python/.gitignore
+++ b/python/.gitignore
@@ -3,3 +3,4 @@
 __pycache__/
 /data/items.json
 /images/*.jpg
+.DS_Store
diff --git a/python/data/items.json b/python/data/items.json
index 0abc0cbb9..a26ceaf9a 100644
--- a/python/data/items.json
+++ b/python/data/items.json
@@ -3,6 +3,76 @@
     {
       "name": "jacket",
       "category": "fashion"
+    },
+    {
+      "name": "jacket",
+      "category": "fashion",
+      "image_name": "ca375b0774eeef70e288368897e8604629660eecc21fa2517e687e35583d0f8b.jpg"
+    },
+    {
+      "name": "jacket",
+      "category": "fashion",
+      "image_name": "ca375b0774eeef70e288368897e8604629660eecc21fa2517e687e35583d0f8b.jpg"
+    },
+    {
+      "name": "jacket",
+      "category": "fashion",
+      "image_name": "ca375b0774eeef70e288368897e8604629660eecc21fa2517e687e35583d0f8b.jpg"
+    },
+    {
+      "name": "jacket",
+      "category": "fashion",
+      "image_name": "ca375b0774eeef70e288368897e8604629660eecc21fa2517e687e35583d0f8b.jpg"
+    },
+    {
+      "name": "jacket",
+      "category": "fashion",
+      "image_name": "ca375b0774eeef70e288368897e8604629660eecc21fa2517e687e35583d0f8b.jpg"
+    },
+    {
+      "name": "jacket",
+      "category": "fashion",
+      "image_name": "ca375b0774eeef70e288368897e8604629660eecc21fa2517e687e35583d0f8b.jpg"
+    },
+    {
+      "name": "jacket",
+      "category": "fashion",
+      "image_name": "ca375b0774eeef70e288368897e8604629660eecc21fa2517e687e35583d0f8b.jpg"
+    },
+    {
+      "name": "jacket",
+      "category": "fashion",
+      "image_name": "ca375b0774eeef70e288368897e8604629660eecc21fa2517e687e35583d0f8b.jpg"
+    },
+    {
+      "name": "jacket",
+      "category": "fashion",
+      "image_name": "ca375b0774eeef70e288368897e8604629660eecc21fa2517e687e35583d0f8b.jpg"
+    },
+    {
+      "name": "jacket",
+      "category": "fashion",
+      "image_name": "ca375b0774eeef70e288368897e8604629660eecc21fa2517e687e35583d0f8b.jpg"
+    },
+    {
+      "name": "jacket",
+      "category": "fashion",
+      "image_name": "ca375b0774eeef70e288368897e8604629660eecc21fa2517e687e35583d0f8b.jpg"
+    },
+    {
+      "name": "jacket",
+      "category": "fashion",
+      "image_name": "ca375b0774eeef70e288368897e8604629660eecc21fa2517e687e35583d0f8b.jpg"
+    },
+    {
+      "name": "jacket",
+      "category": "fashion",
+      "image_name": "ca375b0774eeef70e288368897e8604629660eecc21fa2517e687e35583d0f8b.jpg"
+    },
+    {
+      "name": "jacket",
+      "category": "fashion",
+      "image_name": "ca375b0774eeef70e288368897e8604629660eecc21fa2517e687e35583d0f8b.jpg"
     }
   ]
 }
\ No newline at end of file
diff --git a/python/db/items.sql b/python/db/items.sql
new file mode 100644
index 000000000..21748522a
--- /dev/null
+++ b/python/db/items.sql
@@ -0,0 +1,21 @@
+-- CREATE TABLE IF NOT EXISTS items (
+--     id INTEGER PRIMARY KEY AUTOINCREMENT,
+--     name TEXT NOT NULL,
+--     category TEXT NOT NULL,
+--     image_name TEXT NOT NULL
+-- );
+
+CREATE TABLE IF NOT EXISTS categories (
+    id INTEGER PRIMARY KEY AUTOINCREMENT,
+    name TEXT NOT NULL UNIQUE
+);
+
+CREATE TABLE IF NOT EXISTS items (
+    id INTEGER PRIMARY KEY AUTOINCREMENT,
+    name TEXT NOT NULL,
+    category_id INTEGER NOT NULL,
+    image_name TEXT NOT NULL,
+    FOREIGN KEY (category_id) REFERENCES categories (id)
+);
+
+
diff --git a/python/hello.py b/python/hello.py
new file mode 100644
index 000000000..f7e78cc58
--- /dev/null
+++ b/python/hello.py
@@ -0,0 +1,31 @@
+# def say_hello(name=""):
+#     if name:
+#         return f"Hello, {name}!"
+#     return "Hello!"
+
+# from datetime import datetime
+
+# def say_hello(name):
+#     now = datetime.now() # 現在時刻に直接依存しているため、テストしにくい
+#     current_hour = now.hour
+
+#     if 6 <= current_hour < 10:
+#         return f"Good morning, {name}!"
+#     if 10 <= current_hour < 18:
+#         return f"Hello, {name}!"
+#     return f"Good evening, {name}!"
+
+# 改善されたコード（テストしやすい設計）
+from datetime import datetime
+
+def say_hello(name, now=None):
+    if now is None:
+        now = datetime.now()
+
+    current_hour = now.hour
+
+    if 6 <= current_hour < 10:
+        return f"Good morning, {name}!"
+    if 10 <= current_hour < 18:
+        return f"Hello, {name}!"
+    return f"Good evening, {name}!"
diff --git a/python/hello_test.py b/python/hello_test.py
new file mode 100644
index 000000000..7548fdc9b
--- /dev/null
+++ b/python/hello_test.py
@@ -0,0 +1,41 @@
+# import pytest
+# from hello import say_hello
+
+# @pytest.mark.parametrize("name, expected",[
+#     ("Alice", "Hello, Alice!"),
+#     ("", "Hello!"),
+# ]
+# )
+# def test_say_hello(name, expected):
+#     got = say_hello(name)
+
+#     # 期待する返り値と実際に得た値が同じか確認した上で, 期待する返り値と実際に得た値が異なる場合は、エラーを表示
+#     assert got == expected, f"unexpected result of say_hello: want={expected}, got={got}"
+
+
+import pytest
+from datetime import datetime
+from hello import say_hello
+from fastapi.testclient import TestClient
+from main import app
+
+# client = TestClient(app)
+
+@pytest.mark.parametrize("name, now, expected", [
+    ("Alice", datetime(2024, 1, 1, 9, 0, 0), "Good morning, Alice!"),
+    ("Bob", datetime(2024, 1, 1, 12, 0, 0), "Hello, Bob!"),
+    ("Charlie", datetime(2024, 1, 1, 20, 0, 0), "Good evening, Charlie!"),
+])
+
+# Function test: to confirm the function works as expected (input - output)
+def test_say_hello_simple(name, now, expected):
+    got = say_hello(name, now)
+    assert got == expected, f"unexpected result of say_hello: want={expected}, got={got}"
+
+'''
+# Handler test: to confirm the API works as expected (request - response)
+def test_hello():
+    response = client.get("/")
+    assert response.status_code == 200
+    assert response.json() == {"message": "Hello, world!"}
+'''
diff --git a/python/main.py b/python/main.py
index ffdef4e38..1464346ba 100644
--- a/python/main.py
+++ b/python/main.py
@@ -9,53 +9,99 @@
 import logging
 import pathlib
 import hashlib
-from fastapi import FastAPI, Form, File, UploadFile, HTTPException, Depends
+from fastapi import FastAPI, Form, File, UploadFile, HTTPException, Depends, Query
 from fastapi.responses import FileResponse
 from fastapi.middleware.cors import CORSMiddleware
 import sqlite3
 from pydantic import BaseModel
 from contextlib import asynccontextmanager
 import json
+from typing import Dict, List
 
 
 # Define the path to the images & sqlite3 database
 images_dir = pathlib.Path(__file__).parent.resolve() / "images"
-# db = pathlib.Path(__file__).parent.resolve() / "db" / "mercari.sqlite3"
-items_file = pathlib.Path(__file__).parent.resolve() / "data/items.json"
+db = pathlib.Path(__file__).parent.resolve() / "db" / "mercari.sqlite3"
+# items_file = pathlib.Path(__file__).parent.resolve() / "data/items.json"
 
 # Ensure directories exist
 images_dir.mkdir(parents=True, exist_ok=True)
 
-def get_items():
-    if not items_file.exists():
-        items_file.parent.mkdir(parents=True, exist_ok=True)
-        save_items({"items": []})
+# def get_items():
+#     if not items_file.exists():
+#         items_file.parent.mkdir(parents=True, exist_ok=True)
+#         save_items({"items": []})
 
-    with items_file.open("r") as f:
-        return json.load(f)
+#     with items_file.open("r") as f:
+#         return json.load(f)
 
-def save_items(items):
-    with items_file.open("w") as f:
-        json.dump(items, f, indent=2)
+# def save_items(items):
+#     with items_file.open("w") as f:
+#         json.dump(items, f, indent=2)
 
+def get_items_from_database(db: sqlite3.Connection):
+    cursor = db.cursor()
+    query = """
+    SELECT items.name, categories.name AS category, image_name
+    FROM items
+    JOIN categories
+    ON items.category_id = categories.id
+    """
+    cursor.execute(query)
+    rows = cursor.fetchall()
+    items_list = [{"name": name, "category": category, "image_name": image_name} for name, category, image_name in rows]
+    result = {"items": items_list}
+    cursor.close()
 
-# def get_db():
-#     if not db.exists():
-#         yield
+    return result
 
-#     conn = sqlite3.connect(db)
-#     conn.row_factory = sqlite3.Row  # Return rows as dictionaries
-#     try:
-#         yield conn
-#     finally:
-#         conn.close()
+def get_items_from_database_by_id(id: int, db: sqlite3.Connection) -> Dict[str, List[Dict[str, str]]]:
+    cursor = db.cursor()
+    query = """
+    SELECT items.name, categories.name AS category, image_name
+    FROM items
+    JOIN categories
+    ON items.category_id = categories.id
+    WHERE items.id = ?
+    """
+    cursor.execute(query, (id,))
+    rows = cursor.fetchall()
+    items_list = [{"name": name, "category": category, "image_name": image_name} for name, category, image_name in rows]
+    result = {"items": items_list}
+    cursor.close()
+
+    return result
+
+
+
+def get_db():
+    # db.parent.mkdir(parents=True, exist_ok=True)  # dbフォルダを作成
+    if not db.exists():
+        setup_database()
+
+    # if not db.exists():
+    #     yield
+
+    conn = sqlite3.connect(db, check_same_thread=False)
+    conn.row_factory = sqlite3.Row  # Return rows as dictionaries
+    try:
+        yield conn
+    finally:
+        conn.close()
 
 
 
 
 # STEP 5-1: set up the database connection
 def setup_database():
-    pass
+    conn = sqlite3.connect(db)
+    cursor = conn.cursor()
+    sql_file = pathlib.Path(__file__).parent.resolve() / "db" / "items.sql"
+    with open(sql_file, "r") as f:
+        cursor.executescript(f.read())
+    conn.commit()
+    cursor.close()
+    conn.close()
 
 
 @asynccontextmanager
@@ -104,48 +150,97 @@ class AddItemResponse(BaseModel):
 class Item(BaseModel):
     name: str
     category: str
+    category_id: int = None
     image_name: str
 
+# class Item(BaseModel):
+#     name: str
+#     category: str
+#     image_name: str
+#     category_id: int = None
+
+# @app.get("/items")
+# def get_items_list():
+#     return get_items()
+
 @app.get("/items")
-def get_items_list():
-    return get_items()
+def get_items(db: sqlite3.Connection = Depends(get_db)):
+    return get_items_from_database(db)
 
-@app.get("/items/{item_id}")
-def get_item(item_id: int):
-    items_data = get_items()
-    if "items" not in items_data or not items_data["items"]:
-        raise HTTPException(status_code=404, detail="Item not found")
+# @app.get("/items/{item_id}")
+# def get_item(item_id: int):
+#     items_data = get_items()
+#     if "items" not in items_data or not items_data["items"]:
+#         raise HTTPException(status_code=404, detail="Item not found")
+
+#     items = items_data["items"]
+#     if item_id < 0 or item_id >= len(items):
+#         raise HTTPException(status_code=404, detail=f"Item with ID {item_id} not found")
 
-    items = items_data["items"]
-    if item_id < 0 or item_id >= len(items):
-        raise HTTPException(status_code=404, detail=f"Item with ID {item_id} not found")
+#     return items[item_id]
 
-    return items[item_id]
+# @app.get("/items/{item_id}")
+# def get_item(item_id: int):
+#     items_data = get_items_from_database()
+#     if "items" not in items_data or not items_data["items"]:
+#         raise HTTPException(status_code=404, detail="Item not found")
 
+#     items = items_data["items"]
+#     if item_id < 0 or item_id >= len(items):
+#         raise HTTPException(status_code=404, detail=f"Item with ID {item_id} not found")
+
+#     return items[item_id]
+
+@app.get("/items/{item_id}")
+def get_item(item_id: int, db: sqlite3.Connection = Depends(get_db)):
+    items_data = get_items_from_database_by_id(item_id, db)
+    if not items_data["items"]:
+        raise HTTPException(status_code=404, detail="Item not found")
+
+    return items_data["items"][0]
 
 # add_item is a handler to add a new item for POST /items .
 @app.post("/items", response_model=AddItemResponse)
-async def add_item(
+def add_item(
     name: str = Form(...),
     category: str = Form(...),
-    image: UploadFile = File(...),
-    # db: sqlite3.Connection = Depends(get_db),
+    image: UploadFile = File(None), # image is optional
+    db: sqlite3.Connection = Depends(get_db),
 ):
     if not name:
         raise HTTPException(status_code=400, detail="name is required")
     if not category:
         raise HTTPException(status_code=400, detail="category is required")
-    if not image:
-        raise HTTPException(status_code=400, detail="image is required")
-
-    image_bytes = await image.read()
-    hash_value = hashlib.sha256(image_bytes).hexdigest()
-    image_filename = f"{hash_value}.jpg"
-    image_path = images_dir / image_filename
-    with image_path.open("wb") as f:
-        f.write(image_bytes)
-
-    insert_item(Item(name=name, category=category, image_name=image_filename))
+    # if not image:
+    #     raise HTTPException(status_code=200, detail="image was not uploaded")
+
+    image_filename = "default.jpg"
+    if image:
+        image_bytes = image.file.read()
+        hash_value = hashlib.sha256(image_bytes).hexdigest()
+        image_filename = f"{hash_value}.jpg"
+        image_path = images_dir / image_filename
+        with image_path.open("wb") as f:
+            f.write(image_bytes)
+
+    # Query the category table
+    cursor = db.cursor()
+    query_category = "SELECT id FROM categories WHERE name = ?"
+    cursor.execute(query_category, (category,))
+    rows = cursor.fetchone()
+    if rows is None:
+        insert_query_category = "INSERT INTO categories (name) VALUES (?)"
+        cursor.execute(insert_query_category, (category,))
+        category_id = cursor.lastrowid
+    else:
+        category_id = rows[0]
+
+    item = Item(name=name, category=category, category_id=category_id, image_name=image_filename)
+    # item = Item(name=name, category=category, image_name=image_filename, category_id=category_id)
+    insert_item_db(item, db)
+
+    # insert_item(Item(name=name, category=category, image_name=image_filename))
+    # insert_item_db(Item(name=name, category_id=category, image_name=image_filename), db)
     return AddItemResponse(**{"message": f"name: {name}, image saved as {image_filename}"})
 
 
@@ -165,19 +260,54 @@ async def get_image(image_name):
 
     return FileResponse(image)
 
-
-class Item(BaseModel):
-    name: str
-    category: str
-    image_name: str
-
-
-def insert_item(item: Item):
-    # STEP 4-2: add an implementation to store an item
-    items_data = get_items()
-    if "items" not in items_data:
-        items_data["items"] = []
-
-    items_data["items"].append(item.model_dump())
-
-    save_items(items_data)
+@app.get("/search")
+def search_keyword(keyword: str = Query(...), db: sqlite3.Connection = Depends(get_db)):
+    cursor = db.cursor()
+    query = """
+    SELECT items.name AS name, categories.name AS category, image_name
+    FROM items
+    JOIN categories
+    ON items.category_id = categories.id
+    WHERE items.name LIKE ?
+    """
+    pattern = f"%{keyword}%"
+    cursor.execute(query, (pattern,))
+    rows = cursor.fetchall()
+    items_list = [{"name": name, "category": category, "image_name": image_name} for name, category, image_name in rows]
+    result = {"items": items_list}
+    cursor.close()
+
+    return result
+
+
+# def insert_item(item: Item):
+#     # STEP 4-2: add an implementation to store an item
+#     items_data = get_items()
+#     if "items" not in items_data:
+#         items_data["items"] = []
+
+#     items_data["items"].append(item.model_dump())
+
+#     save_items(items_data)
+
+def insert_item_db(item: Item, db: sqlite3.Connection) -> int:
+    cursor = db.cursor()
+    # # Query the category table
+    # query_category = "SELECT id FROM categories WHERE name = ?"
+    # cursor.execute(query_category, (item.category,))
+    # rows = cursor.fetchone()
+    # if rows is None:
+    #     insert_query_category = "INSERT INTO categories (name) VALUES (?)"
+    #     cursor.execute(insert_query_category, (item.category,))
+    #     category_id = cursor.lastrowid
+    # else:
+    #     category_id = rows[0]
+
+    query = """
+    INSERT INTO items (name, category_id, image_name) VALUES (?, ?, ?);
+    """
+    cursor.execute(query, (item.name, item.category_id, item.image_name))
+
+    db.commit()
+    cursor.close()
+    return cursor.lastrowid
diff --git a/python/main_test.py b/python/main_test.py
index 21271f0e3..f426fe7bd 100644
--- a/python/main_test.py
+++ b/python/main_test.py
@@ -1,3 +1,7 @@
+'''
+Test code for main.py
+'''
+
 from fastapi.testclient import TestClient
 from main import app, get_db
 import pytest
@@ -6,39 +10,54 @@
 import pathlib
 
 # STEP 6-4: uncomment this test setup
-# test_db = pathlib.Path(__file__).parent.resolve() / "db" / "test_mercari.sqlite3"
-
-# def override_get_db():
-#     conn = sqlite3.connect(test_db)
-#     conn.row_factory = sqlite3.Row
-#     try:
-#         yield conn
-#     finally:
-#         conn.close()
-
-# app.dependency_overrides[get_db] = override_get_db
-
-# @pytest.fixture(autouse=True)
-# def db_connection():
-#     # Before the test is done, create a test database
-#     conn = sqlite3.connect(test_db)
-#     cursor = conn.cursor()
-#     cursor.execute(
-#         """CREATE TABLE IF NOT EXISTS items (
-# 		id INTEGER PRIMARY KEY AUTOINCREMENT,
-# 		name VARCHAR(255),
-# 		category VARCHAR(255)
-# 	)"""
-#     )
-#     conn.commit()
-#     conn.row_factory = sqlite3.Row  # Return rows as dictionaries
-
-#     yield conn
-
-#     conn.close()
-#     # After the test is done, remove the test database
-#     if test_db.exists():
-#         test_db.unlink() # Remove the file
+test_db = pathlib.Path(__file__).parent.resolve() / "db" / "test_mercari.sqlite3"
+
+def setup_test_database():
+    conn = sqlite3.connect(test_db)
+    cursor = conn.cursor()
+    sql_file = pathlib.Path(__file__).parent.resolve() / "db" / "items.sql"
+    with open(sql_file, "r") as f:
+        cursor.executescript(f.read())
+    conn.commit()
+    cursor.close()
+    conn.close()
+
+def override_get_db():
+    if not test_db.exists():
+        setup_test_database()
+
+    conn = sqlite3.connect(test_db)
+    conn.row_factory = sqlite3.Row
+    try:
+        yield conn
+    finally:
+        conn.close()
+
+app.dependency_overrides[get_db] = override_get_db
+
+@pytest.fixture(autouse=True)
+def db_connection():
+    # Before the test is done, create a test database
+    setup_test_database()
+    conn = sqlite3.connect(test_db)
+    # cursor = conn.cursor()
+
+    # cursor.execute(
+    #     """CREATE TABLE IF NOT EXISTS items (
+	# 	id INTEGER PRIMARY KEY AUTOINCREMENT,
+	# 	name VARCHAR(255),
+	# 	category VARCHAR(255)
+	# )"""
+    # )
+    # conn.commit()
+    conn.row_factory = sqlite3.Row  # Return rows as dictionaries
+
+    yield conn
+
+    conn.close()
+    # After the test is done, remove the test database
+    if test_db.exists():
+        test_db.unlink() # Remove the file
 
 client = TestClient(app)
 
@@ -50,34 +69,36 @@
     ],
 )
 def test_hello(want_status_code, want_body):
-    response_body = client.get("/").json()
+    response_body = client.get("/")
     # STEP 6-2: confirm the status code
+    assert response_body.status_code == want_status_code, f"Expected status code {want_status_code}, got {response_body.status_code}"
     # STEP 6-2: confirm response body
+    assert response_body.json() == want_body, f"Expected response body {want_body}, got {response_body.json()}"
 
 
 # STEP 6-4: uncomment this test
-# @pytest.mark.parametrize(
-#     "args, want_status_code",
-#     [
-#         ({"name":"used iPhone 16e", "category":"phone"}, 200),
-#         ({"name":"", "category":"phone"}, 400),
-#     ],
-# )
-# def test_add_item_e2e(args,want_status_code,db_connection):
-#     response = client.post("/items/", data=args)
-#     assert response.status_code == want_status_code
-    
-#     if want_status_code >= 400:
-#         return
-    
-    
-#     # Check if the response body is correct
-#     response_data = response.json()
-#     assert "message" in response_data
-
-#     # Check if the data was saved to the database correctly
-#     cursor = db_connection.cursor()
-#     cursor.execute("SELECT * FROM items WHERE name = ?", (args["name"],))
-#     db_item = cursor.fetchone()
-#     assert db_item is not None
-#     assert dict(db_item)["name"] == args["name"]
+@pytest.mark.parametrize(
+    "args, want_status_code",
+    [
+        ({"name":"used iPhone 16e", "category":"phone"}, 200),
+        ({"name":"", "category":"phone"}, 400),
+    ],
+)
+def test_add_item_e2e(args,want_status_code,db_connection):
+    response = client.post("/items/", data=args)
+    assert response.status_code == want_status_code
+
+    if want_status_code >= 400:
+        return
+
+
+    # Check if the response body is correct
+    response_data = response.json()
+    assert "message" in response_data
+
+    # Check if the data was saved to the database correctly
+    cursor = db_connection.cursor()
+    cursor.execute("SELECT * FROM items WHERE name = ?", (args["name"],))
+    db_item = cursor.fetchone()
+    assert db_item is not None
+    assert dict(db_item)["name"] == args["name"]


Reviews:
user: login: wakuchan
body: LGTMです:)
state: APPROVED
author_association: NONE
submitted_at: 2025-03-17T02:49:20Z
user: login: mimo-owl
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-24T06:29:13Z

Review comments:
diff_hunk: @@ -50,34 +69,36 @@
     ],
 )
 def test_hello(want_status_code, want_body):
-    response_body = client.get("/").json()
+    response_body = client.get("/")
     # STEP 6-2: confirm the status code
+    assert response_body.status_code == want_status_code, f"Expected status code {want_status_code}, got {response_body.status_code}"
     # STEP 6-2: confirm response body
+    assert response_body.json() == want_body, f"Expected response body {want_body}, got {response_body.json()}"
 
 
 # STEP 6-4: uncomment this test
-# @pytest.mark.parametrize(
-#     "args, want_status_code",
-#     [
-#         ({"name":"used iPhone 16e", "category":"phone"}, 200),
-#         ({"name":"", "category":"phone"}, 400),
-#     ],
-# )
-# def test_add_item_e2e(args,want_status_code,db_connection):
-#     response = client.post("/items/", data=args)
-#     assert response.status_code == want_status_code
-    
-#     if want_status_code >= 400:
-#         return
-    
-    
-#     # Check if the response body is correct
-#     response_data = response.json()
-#     assert "message" in response_data
-
-#     # Check if the data was saved to the database correctly
-#     cursor = db_connection.cursor()
-#     cursor.execute("SELECT * FROM items WHERE name = ?", (args["name"],))
-#     db_item = cursor.fetchone()
-#     assert db_item is not None
-#     assert dict(db_item)["name"] == args["name"]
+@pytest.mark.parametrize(
+    "args, want_status_code",
+    [
+        ({"name":"used iPhone 16e", "category":"phone"}, 200),
+        ({"name":"", "category":"phone"}, 400),
path: python/main_test.py
user: login: wakuchan
body: テストは網羅的にテストしたいので、categoryが""だった場合などもあるとより良いですね！
created_at: 2025-03-17T02:49:09Z
author_association: NONE
diff_hunk: @@ -50,34 +69,36 @@
     ],
 )
 def test_hello(want_status_code, want_body):
-    response_body = client.get("/").json()
+    response_body = client.get("/")
     # STEP 6-2: confirm the status code
+    assert response_body.status_code == want_status_code, f"Expected status code {want_status_code}, got {response_body.status_code}"
     # STEP 6-2: confirm response body
+    assert response_body.json() == want_body, f"Expected response body {want_body}, got {response_body.json()}"
 
 
 # STEP 6-4: uncomment this test
-# @pytest.mark.parametrize(
-#     "args, want_status_code",
-#     [
-#         ({"name":"used iPhone 16e", "category":"phone"}, 200),
-#         ({"name":"", "category":"phone"}, 400),
-#     ],
-# )
-# def test_add_item_e2e(args,want_status_code,db_connection):
-#     response = client.post("/items/", data=args)
-#     assert response.status_code == want_status_code
-    
-#     if want_status_code >= 400:
-#         return
-    
-    
-#     # Check if the response body is correct
-#     response_data = response.json()
-#     assert "message" in response_data
-
-#     # Check if the data was saved to the database correctly
-#     cursor = db_connection.cursor()
-#     cursor.execute("SELECT * FROM items WHERE name = ?", (args["name"],))
-#     db_item = cursor.fetchone()
-#     assert db_item is not None
-#     assert dict(db_item)["name"] == args["name"]
+@pytest.mark.parametrize(
+    "args, want_status_code",
+    [
+        ({"name":"used iPhone 16e", "category":"phone"}, 200),
+        ({"name":"", "category":"phone"}, 400),
path: python/main_test.py
user: login: mimo-owl
body: コメントありがとうございます。確かにおっしゃる通りです。
このブランチは一旦マージしますが、step9 のPRで反映させます！
created_at: 2025-03-24T06:29:13Z
author_association: OWNER

PR comments:
user: login: mimo-owl
created_at: 2025-03-14T02:21:52Z
body: I realized that I mistakenly stated ‘finished step5_0,’ but I actually meant step6_0. Apologies for any confusion!
author_association: OWNER
user: login: mimo-owl
created_at: 2025-03-16T00:27:16Z
body: step6-2
<img width="591" alt="Screenshot 2025-03-16 at 9 26 27" src="https://github.com/user-attachments/assets/85dedd5d-86e7-4f0b-bc1f-1ec5b24fa04c" />

author_association: OWNER
user: login: mimo-owl
created_at: 2025-03-16T01:09:49Z
body: step6-4
<img width="591" alt="Screenshot 2025-03-16 at 10 03 34" src="https://github.com/user-attachments/assets/767f2f47-aa79-41f2-a03b-b32c9a629867" />

author_association: OWNER
user: login: mimo-owl
created_at: 2025-03-16T01:10:49Z
body: Thank you for the comment. 
I fixed some codes and confirmed that execution with "pytest main_test.py" works for all the test cases!
author_association: OWNER