Remove obsolete scripts and files, update site builder configurations
- Deleted the `import_plans.py`, `run_tests.py`, and `test_run.py` scripts as they are no longer needed. - Updated the initial migration dependency in `0001_initial.py` to reflect recent changes in the `igny8_core_auth` app. - Enhanced the implementation plan documentation to include new phases and updates on the site builder project. - Updated the `vite.config.ts` and `package.json` to integrate testing configurations and dependencies for the site builder.
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from igny8_core.ai.functions.generate_site_structure import GenerateSiteStructureFunction
|
||||
from igny8_core.business.site_building.models import PageBlueprint
|
||||
from igny8_core.business.site_building.tests.base import SiteBuilderTestBase
|
||||
|
||||
|
||||
class GenerateSiteStructureFunctionTests(SiteBuilderTestBase):
|
||||
"""Covers parsing + persistence logic for the Site Builder AI function."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.function = GenerateSiteStructureFunction()
|
||||
|
||||
def test_parse_response_extracts_json_object(self):
|
||||
noisy_response = """
|
||||
Thoughts about the request…
|
||||
{
|
||||
"site": {"name": "Acme Robotics"},
|
||||
"pages": [{"slug": "home", "title": "Home"}]
|
||||
}
|
||||
Extra commentary that should be ignored.
|
||||
"""
|
||||
parsed = self.function.parse_response(noisy_response)
|
||||
self.assertEqual(parsed['site']['name'], 'Acme Robotics')
|
||||
self.assertEqual(parsed['pages'][0]['slug'], 'home')
|
||||
|
||||
def test_save_output_updates_structure_and_syncs_pages(self):
|
||||
# Existing page to prove update/delete flows.
|
||||
legacy_page = PageBlueprint.objects.create(
|
||||
site_blueprint=self.blueprint,
|
||||
slug='legacy',
|
||||
title='Legacy Page',
|
||||
type='custom',
|
||||
blocks_json=[],
|
||||
order=5,
|
||||
)
|
||||
|
||||
parsed = {
|
||||
'site': {'name': 'Future Robotics'},
|
||||
'pages': [
|
||||
{
|
||||
'slug': 'home',
|
||||
'title': 'Homepage',
|
||||
'type': 'home',
|
||||
'status': 'ready',
|
||||
'blocks': [{'type': 'hero', 'heading': 'Build faster'}],
|
||||
},
|
||||
{
|
||||
'slug': 'about',
|
||||
'title': 'About Us',
|
||||
'type': 'about',
|
||||
'blocks': [],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
result = self.function.save_output(parsed, {'blueprint': self.blueprint})
|
||||
|
||||
self.blueprint.refresh_from_db()
|
||||
self.assertEqual(self.blueprint.status, 'ready')
|
||||
self.assertEqual(self.blueprint.structure_json['site']['name'], 'Future Robotics')
|
||||
self.assertEqual(result['pages_created'], 1)
|
||||
self.assertEqual(result['pages_updated'], 1)
|
||||
self.assertEqual(result['pages_deleted'], 1)
|
||||
|
||||
slugs = set(self.blueprint.pages.values_list('slug', flat=True))
|
||||
self.assertIn('home', slugs)
|
||||
self.assertIn('about', slugs)
|
||||
self.assertNotIn(legacy_page.slug, slugs)
|
||||
|
||||
def test_build_prompt_includes_existing_pages(self):
|
||||
# Convert structure to JSON to ensure template rendering stays stable.
|
||||
data = self.function.prepare(
|
||||
payload={'ids': [self.blueprint.id]},
|
||||
account=self.account,
|
||||
)
|
||||
prompt = self.function.build_prompt(data, account=self.account)
|
||||
self.assertIn(self.blueprint.name, prompt)
|
||||
self.assertIn('Home', prompt)
|
||||
# The prompt should mention hosting type and objectives in JSON context.
|
||||
self.assertIn(self.blueprint.hosting_type, prompt)
|
||||
for objective in self.blueprint.config_json.get('objectives', []):
|
||||
self.assertIn(objective, prompt)
|
||||
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
"""
|
||||
Test script for AI functions
|
||||
Run this to verify all AI functions work with console logging
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
|
||||
# Setup Django
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../../../'))
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8.settings')
|
||||
django.setup()
|
||||
|
||||
from igny8_core.ai.functions.auto_cluster import AutoClusterFunction
|
||||
from igny8_core.ai.functions.generate_images import generate_images_core
|
||||
from igny8_core.ai.ai_core import AICore
|
||||
|
||||
|
||||
def test_ai_core():
|
||||
"""Test AICore.run_ai_request() directly"""
|
||||
print("\n" + "="*80)
|
||||
print("TEST 1: AICore.run_ai_request() - Direct API Call")
|
||||
print("="*80)
|
||||
|
||||
ai_core = AICore()
|
||||
result = ai_core.run_ai_request(
|
||||
prompt="Say 'Hello, World!' in JSON format: {\"message\": \"your message\"}",
|
||||
max_tokens=100,
|
||||
function_name='test_ai_core'
|
||||
)
|
||||
|
||||
if result.get('error'):
|
||||
print(f"❌ Error: {result['error']}")
|
||||
else:
|
||||
print(f"✅ Success! Content: {result.get('content', '')[:100]}")
|
||||
print(f" Tokens: {result.get('total_tokens')}, Cost: ${result.get('cost', 0):.6f}")
|
||||
|
||||
|
||||
def test_auto_cluster():
|
||||
"""Test auto cluster function"""
|
||||
print("\n" + "="*80)
|
||||
print("TEST 2: Auto Cluster Function")
|
||||
print("="*80)
|
||||
print("Note: This requires actual keyword IDs in the database")
|
||||
print("Skipping - requires database setup")
|
||||
# Uncomment to test with real data:
|
||||
# fn = AutoClusterFunction()
|
||||
# result = fn.validate({'ids': [1, 2, 3]})
|
||||
# print(f"Validation result: {result}")
|
||||
|
||||
|
||||
def test_generate_content():
|
||||
"""Test generate content function"""
|
||||
print("\n" + "="*80)
|
||||
print("TEST 3: Generate Content Function")
|
||||
print("="*80)
|
||||
print("Note: This requires actual task IDs in the database")
|
||||
print("Skipping - requires database setup")
|
||||
|
||||
|
||||
def test_generate_images():
|
||||
"""Test generate images function"""
|
||||
print("\n" + "="*80)
|
||||
print("TEST 4: Generate Images Function")
|
||||
print("="*80)
|
||||
print("Note: This requires actual task IDs in the database")
|
||||
print("Skipping - requires database setup")
|
||||
# Uncomment to test with real data:
|
||||
# result = generate_images_core(task_ids=[1], account_id=1)
|
||||
# print(f"Result: {result}")
|
||||
|
||||
|
||||
def test_json_extraction():
|
||||
"""Test JSON extraction"""
|
||||
print("\n" + "="*80)
|
||||
print("TEST 5: JSON Extraction")
|
||||
print("="*80)
|
||||
|
||||
ai_core = AICore()
|
||||
|
||||
# Test 1: Direct JSON
|
||||
json_text = '{"clusters": [{"name": "Test", "keywords": ["test"]}]}'
|
||||
result = ai_core.extract_json(json_text)
|
||||
print(f"✅ Direct JSON: {result is not None}")
|
||||
|
||||
# Test 2: JSON in markdown
|
||||
json_markdown = '```json\n{"clusters": [{"name": "Test"}]}\n```'
|
||||
result = ai_core.extract_json(json_markdown)
|
||||
print(f"✅ JSON in markdown: {result is not None}")
|
||||
|
||||
# Test 3: Invalid JSON
|
||||
invalid_json = "This is not JSON"
|
||||
result = ai_core.extract_json(invalid_json)
|
||||
print(f"✅ Invalid JSON handled: {result is None}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("\n" + "="*80)
|
||||
print("AI FUNCTIONS TEST SUITE")
|
||||
print("="*80)
|
||||
print("Testing all AI functions with console logging enabled")
|
||||
print("="*80)
|
||||
|
||||
# Run tests
|
||||
test_ai_core()
|
||||
test_json_extraction()
|
||||
test_auto_cluster()
|
||||
test_generate_content()
|
||||
test_generate_images()
|
||||
|
||||
print("\n" + "="*80)
|
||||
print("TEST SUITE COMPLETE")
|
||||
print("="*80)
|
||||
print("\nAll console logging should be visible above.")
|
||||
print("Check for [AI][function_name] Step X: messages")
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Test runner script for API tests
|
||||
Run all tests: python manage.py test igny8_core.api.tests
|
||||
Run specific test: python manage.py test igny8_core.api.tests.test_response
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
|
||||
# Setup Django
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
|
||||
django.setup()
|
||||
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Run all API tests
|
||||
if len(sys.argv) > 1:
|
||||
# Custom test specified
|
||||
execute_from_command_line(['manage.py', 'test'] + sys.argv[1:])
|
||||
else:
|
||||
# Run all API tests
|
||||
execute_from_command_line(['manage.py', 'test', 'igny8_core.api.tests', '--verbosity=2'])
|
||||
|
||||
@@ -8,7 +8,7 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0008_passwordresettoken_alter_industry_options_and_more'),
|
||||
('igny8_core_auth', '0014_remove_plan_operation_limits_phase0'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
|
||||
90
backend/igny8_core/business/site_building/tests/base.py
Normal file
90
backend/igny8_core/business/site_building/tests/base.py
Normal file
@@ -0,0 +1,90 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from igny8_core.auth.models import (
|
||||
Account,
|
||||
Industry,
|
||||
IndustrySector,
|
||||
Plan,
|
||||
Sector,
|
||||
Site,
|
||||
User,
|
||||
)
|
||||
from igny8_core.business.site_building.models import PageBlueprint, SiteBlueprint
|
||||
|
||||
|
||||
class SiteBuilderTestBase(TestCase):
|
||||
"""
|
||||
Provides a lightweight set of fixtures (account/site/sector/blueprint)
|
||||
so Site Builder tests can focus on service logic instead of boilerplate.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.plan = Plan.objects.create(
|
||||
name='Test Plan',
|
||||
slug='test-plan',
|
||||
price=Decimal('0.00'),
|
||||
included_credits=1000,
|
||||
)
|
||||
self.user = User.objects.create_user(
|
||||
username='blueprint-owner',
|
||||
email='owner@example.com',
|
||||
password='testpass123',
|
||||
role='owner',
|
||||
)
|
||||
self.account = Account.objects.create(
|
||||
name='Site Builder Account',
|
||||
slug='site-builder-account',
|
||||
owner=self.user,
|
||||
plan=self.plan,
|
||||
)
|
||||
self.user.account = self.account
|
||||
self.user.save()
|
||||
|
||||
self.industry = Industry.objects.create(name='Automation', slug='automation')
|
||||
self.industry_sector = IndustrySector.objects.create(
|
||||
industry=self.industry,
|
||||
name='Robotics',
|
||||
slug='robotics',
|
||||
)
|
||||
self.site = Site.objects.create(
|
||||
name='Acme Robotics',
|
||||
slug='acme-robotics',
|
||||
account=self.account,
|
||||
industry=self.industry,
|
||||
)
|
||||
self.sector = Sector.objects.create(
|
||||
site=self.site,
|
||||
industry_sector=self.industry_sector,
|
||||
name='Warehouse Automation',
|
||||
slug='warehouse-automation',
|
||||
account=self.account,
|
||||
)
|
||||
|
||||
self.blueprint = SiteBlueprint.objects.create(
|
||||
site=self.site,
|
||||
sector=self.sector,
|
||||
name='Core Blueprint',
|
||||
description='Initial blueprint used for tests',
|
||||
hosting_type='igny8_sites',
|
||||
config_json={
|
||||
'business_brief': 'Default brief',
|
||||
'objectives': ['Drive demos'],
|
||||
'style': {'palette': 'bold'},
|
||||
},
|
||||
)
|
||||
self.page_blueprint = PageBlueprint.objects.create(
|
||||
site_blueprint=self.blueprint,
|
||||
slug='home',
|
||||
title='Home',
|
||||
type='home',
|
||||
blocks_json=[{'type': 'hero', 'heading': 'Welcome'}],
|
||||
status='draft',
|
||||
order=0,
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from igny8_core.business.billing.exceptions import InsufficientCreditsError
|
||||
from igny8_core.business.content.models import Tasks
|
||||
from igny8_core.business.site_building.services.page_generation_service import PageGenerationService
|
||||
from igny8_core.business.site_building.services.structure_generation_service import (
|
||||
StructureGenerationService,
|
||||
)
|
||||
|
||||
from .base import SiteBuilderTestBase
|
||||
|
||||
|
||||
class StructureGenerationServiceTests(SiteBuilderTestBase):
|
||||
"""Covers the orchestration path for generating site structures."""
|
||||
|
||||
@patch('igny8_core.ai.tasks.run_ai_task')
|
||||
@patch('igny8_core.business.site_building.services.structure_generation_service.CreditService.check_credits')
|
||||
def test_generate_structure_updates_config_and_dispatches_task(self, mock_check, mock_run_ai):
|
||||
mock_async_result = MagicMock()
|
||||
mock_async_result.id = 'celery-123'
|
||||
mock_run_ai.delay.return_value = mock_async_result
|
||||
|
||||
service = StructureGenerationService()
|
||||
payload = {
|
||||
'business_brief': 'We build autonomous fulfillment robots.',
|
||||
'objectives': ['Book more demos'],
|
||||
'style_preferences': {'palette': 'cool', 'personality': 'optimistic'},
|
||||
'metadata': {'requested_by': 'integration-test'},
|
||||
}
|
||||
|
||||
result = service.generate_structure(self.blueprint, **payload)
|
||||
|
||||
self.assertTrue(result['success'])
|
||||
self.assertEqual(result['task_id'], 'celery-123')
|
||||
mock_check.assert_called_once_with(self.account, 'site_structure_generation')
|
||||
mock_run_ai.delay.assert_called_once()
|
||||
|
||||
self.blueprint.refresh_from_db()
|
||||
self.assertEqual(self.blueprint.status, 'generating')
|
||||
self.assertEqual(self.blueprint.config_json['business_brief'], payload['business_brief'])
|
||||
self.assertEqual(self.blueprint.config_json['objectives'], payload['objectives'])
|
||||
self.assertEqual(self.blueprint.config_json['style'], payload['style_preferences'])
|
||||
self.assertIn('last_requested_at', self.blueprint.config_json)
|
||||
self.assertEqual(self.blueprint.config_json['metadata'], payload['metadata'])
|
||||
|
||||
@patch('igny8_core.business.site_building.services.structure_generation_service.CreditService.check_credits')
|
||||
def test_generate_structure_rolls_back_when_insufficient_credits(self, mock_check):
|
||||
mock_check.side_effect = InsufficientCreditsError('No credits remaining')
|
||||
service = StructureGenerationService()
|
||||
|
||||
with self.assertRaises(InsufficientCreditsError):
|
||||
service.generate_structure(
|
||||
self.blueprint,
|
||||
business_brief='Too expensive request',
|
||||
)
|
||||
|
||||
self.blueprint.refresh_from_db()
|
||||
self.assertEqual(self.blueprint.status, 'draft')
|
||||
|
||||
|
||||
class PageGenerationServiceTests(SiteBuilderTestBase):
|
||||
"""Ensures Site Builder pages correctly leverage the Writer pipeline."""
|
||||
|
||||
@patch('igny8_core.business.site_building.services.page_generation_service.ContentGenerationService.generate_content')
|
||||
def test_generate_page_content_creates_writer_task(self, mock_generate_content):
|
||||
mock_generate_content.return_value = {'success': True}
|
||||
service = PageGenerationService()
|
||||
|
||||
result = service.generate_page_content(self.page_blueprint)
|
||||
|
||||
created_task = Tasks.objects.get()
|
||||
expected_title = '[Site Builder] Home'
|
||||
self.assertEqual(created_task.title, expected_title)
|
||||
mock_generate_content.assert_called_once_with([created_task.id], self.account)
|
||||
self.page_blueprint.refresh_from_db()
|
||||
self.assertEqual(self.page_blueprint.status, 'generating')
|
||||
self.assertEqual(result, {'success': True})
|
||||
|
||||
@patch('igny8_core.business.site_building.services.page_generation_service.ContentGenerationService.generate_content')
|
||||
def test_regenerate_page_replaces_writer_task(self, mock_generate_content):
|
||||
mock_generate_content.return_value = {'success': True}
|
||||
service = PageGenerationService()
|
||||
|
||||
first_result = service.generate_page_content(self.page_blueprint)
|
||||
first_task_id = Tasks.objects.get().id
|
||||
self.assertEqual(first_result, {'success': True})
|
||||
|
||||
second_result = service.regenerate_page(self.page_blueprint)
|
||||
second_task = Tasks.objects.get()
|
||||
self.assertEqual(second_result, {'success': True})
|
||||
self.assertNotEqual(first_task_id, second_task.id)
|
||||
self.assertEqual(Tasks.objects.count(), 1)
|
||||
self.assertEqual(mock_generate_content.call_count, 2)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user