fix: extend source CHECK constraints to include visual_capture (kiwi#79)
Migrations 037 and 038 rebuild products and inventory_items tables to add 'visual_capture' as a valid source value, which the label-confirm endpoint sets when saving user-verified nutrition label data. Adds 2 schema tests covering the new allowed value.
This commit is contained in:
parent
0bac494ecd
commit
e2c358c90a
3 changed files with 113 additions and 0 deletions
34
app/db/migrations/037_products_source_visual_capture.sql
Normal file
34
app/db/migrations/037_products_source_visual_capture.sql
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
-- Migration 037: add 'visual_capture' to products.source CHECK constraint
|
||||||
|
-- SQLite cannot ALTER a CHECK constraint, so we rebuild the table.
|
||||||
|
|
||||||
|
PRAGMA foreign_keys = OFF;
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
CREATE TABLE products_new (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
barcode TEXT UNIQUE,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
brand TEXT,
|
||||||
|
category TEXT,
|
||||||
|
description TEXT,
|
||||||
|
image_url TEXT,
|
||||||
|
nutrition_data TEXT NOT NULL DEFAULT '{}',
|
||||||
|
source TEXT NOT NULL DEFAULT 'openfoodfacts'
|
||||||
|
CHECK (source IN ('openfoodfacts', 'manual', 'receipt_ocr', 'visual_capture')),
|
||||||
|
source_data TEXT,
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO products_new
|
||||||
|
SELECT id, barcode, name, brand, category, description, image_url,
|
||||||
|
nutrition_data, source, source_data, created_at, updated_at
|
||||||
|
FROM products;
|
||||||
|
|
||||||
|
DROP TABLE products;
|
||||||
|
ALTER TABLE products_new RENAME TO products;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
PRAGMA foreign_keys = ON;
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
-- Migration 038: add 'visual_capture' to inventory_items.source CHECK constraint
|
||||||
|
-- SQLite cannot ALTER a CHECK constraint, so we rebuild the table.
|
||||||
|
|
||||||
|
PRAGMA foreign_keys = OFF;
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
CREATE TABLE inventory_items_new (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
product_id INTEGER NOT NULL
|
||||||
|
REFERENCES products (id) ON DELETE RESTRICT,
|
||||||
|
receipt_id INTEGER
|
||||||
|
REFERENCES receipts (id) ON DELETE SET NULL,
|
||||||
|
quantity REAL NOT NULL DEFAULT 1 CHECK (quantity > 0),
|
||||||
|
unit TEXT NOT NULL DEFAULT 'count',
|
||||||
|
location TEXT NOT NULL,
|
||||||
|
sublocation TEXT,
|
||||||
|
purchase_date TEXT,
|
||||||
|
expiration_date TEXT,
|
||||||
|
status TEXT NOT NULL DEFAULT 'available'
|
||||||
|
CHECK (status IN ('available', 'consumed', 'expired', 'discarded')),
|
||||||
|
consumed_at TEXT,
|
||||||
|
notes TEXT,
|
||||||
|
source TEXT NOT NULL DEFAULT 'manual'
|
||||||
|
CHECK (source IN ('barcode_scan', 'manual', 'receipt', 'visual_capture')),
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
opened_date TEXT,
|
||||||
|
disposal_reason TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO inventory_items_new
|
||||||
|
SELECT id, product_id, receipt_id, quantity, unit, location, sublocation,
|
||||||
|
purchase_date, expiration_date, status, consumed_at, notes, source,
|
||||||
|
created_at, updated_at, opened_date, disposal_reason
|
||||||
|
FROM inventory_items;
|
||||||
|
|
||||||
|
DROP TABLE inventory_items;
|
||||||
|
ALTER TABLE inventory_items_new RENAME TO inventory_items;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
PRAGMA foreign_keys = ON;
|
||||||
|
|
@ -11,6 +11,42 @@ def store(tmp_path: Path) -> Store:
|
||||||
s.close()
|
s.close()
|
||||||
|
|
||||||
|
|
||||||
|
class TestProductsSchema:
|
||||||
|
"""Verify migrations 037/038 allow 'visual_capture' as a valid source."""
|
||||||
|
|
||||||
|
def test_products_accepts_visual_capture_source(self, store):
|
||||||
|
"""Migration 037: products.source CHECK includes 'visual_capture'."""
|
||||||
|
store.conn.execute(
|
||||||
|
"INSERT INTO products (name, source) VALUES (?, ?)",
|
||||||
|
("Test Product", "visual_capture"),
|
||||||
|
)
|
||||||
|
store.conn.commit()
|
||||||
|
row = store.conn.execute(
|
||||||
|
"SELECT source FROM products WHERE name='Test Product'"
|
||||||
|
).fetchone()
|
||||||
|
assert row[0] == "visual_capture"
|
||||||
|
|
||||||
|
def test_inventory_items_accepts_visual_capture_source(self, store):
|
||||||
|
"""Migration 038: inventory_items.source CHECK includes 'visual_capture'."""
|
||||||
|
store.conn.execute(
|
||||||
|
"INSERT INTO products (name, source) VALUES (?, ?)",
|
||||||
|
("Temp Product", "manual"),
|
||||||
|
)
|
||||||
|
store.conn.commit()
|
||||||
|
product_id = store.conn.execute(
|
||||||
|
"SELECT id FROM products WHERE name='Temp Product'"
|
||||||
|
).fetchone()[0]
|
||||||
|
store.conn.execute(
|
||||||
|
"INSERT INTO inventory_items (product_id, location, source) VALUES (?, ?, ?)",
|
||||||
|
(product_id, "pantry", "visual_capture"),
|
||||||
|
)
|
||||||
|
store.conn.commit()
|
||||||
|
row = store.conn.execute(
|
||||||
|
"SELECT source FROM inventory_items WHERE product_id=?", (product_id,)
|
||||||
|
).fetchone()
|
||||||
|
assert row[0] == "visual_capture"
|
||||||
|
|
||||||
|
|
||||||
class TestMigration:
|
class TestMigration:
|
||||||
def test_captured_products_table_exists(self, store):
|
def test_captured_products_table_exists(self, store):
|
||||||
cur = store.conn.execute(
|
cur = store.conn.execute(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue