feat: dead-config sweep, test suite foundation, widget settingsSchema validation
Some checks failed
Architecture Governance / architecture (push) Has been cancelled

Sprint G: audited every BootstrapConfig field for a real runtime consumer
(docs/DEAD-CONFIG-AUDIT.md). Wired 3 previously-dead editable fields:
footer.logoUrl, company.address.street/contacts.phone, catalog.suggestionsEnabled.
Remaining dead fields needing a business/design decision tracked in
PRODUCT_BACKLOG.md/KNOWN-ISSUES.md, not silently left.

Sprint H: 6 new spec files (test count 57 -> 83), covering ProjectEditorFacade
(undo/redo, draft persistence, publish gating), AdminAnalyticsFacade
(never-fabricate-a-number contract), and regression coverage for this
session's carousel/hero/profile-toggle fixes.

Sprint I: widget settingsSchema (declared in widget-manifest.json, never
validated) now enforced via a new lightweight schema check in
ProjectValidator, surfaced through the existing issuesByField pipeline.
Same check reused in diagnostics so editor and diagnostics can't disagree.

Verification: tsc clean, ng build clean, 83/83 tests pass, barry-cache
validate clean (2 pre-existing unrelated warnings only).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
sdarbinyan
2026-08-05 20:47:13 +04:00
parent 6f9401fa8f
commit ce63931bc2
25 changed files with 814 additions and 25 deletions

View File

@@ -0,0 +1,98 @@
import { TestBed } from '@angular/core/testing';
import { of } from 'rxjs';
import { AdminAnalyticsFacade } from './admin-analytics.facade';
import { AdminOrdersLocalGateway } from '../../orders/services/admin-orders-local.gateway';
import { AdminProductsLocalGateway } from '../../products/services/admin-products-local.gateway';
import { ADMIN_CATEGORIES_GATEWAY } from '../../categories/services/admin-categories-gateway.token';
import { AdminModerationLocalGateway } from '../../moderation/services/admin-moderation-local.gateway';
import { AdminDashboardFacade } from '../../dashboard/facade/admin-dashboard.facade';
import { AdminOrder } from '../../orders/models/admin-order.model';
function makeOrder(overrides: Partial<AdminOrder> = {}): AdminOrder {
return {
id: 'order-1',
createdAt: new Date().toISOString(),
status: 'completed',
total: 100,
currency: 'RUB',
customer: { email: 'buyer@example.com' },
items: [{ productId: 'p1', name: 'Widget', quantity: 1, price: 100 }],
...overrides,
} as unknown as AdminOrder;
}
describe('AdminAnalyticsFacade (never-fabricate-a-number contract)', () => {
let facade: AdminAnalyticsFacade;
let ordersGateway: jasmine.SpyObj<AdminOrdersLocalGateway>;
let productsGateway: jasmine.SpyObj<AdminProductsLocalGateway>;
let categoriesGateway: jasmine.SpyObj<{ loadCategories: () => unknown }>;
let moderationGateway: jasmine.SpyObj<AdminModerationLocalGateway>;
let dashboardFacade: jasmine.SpyObj<AdminDashboardFacade>;
function configure(bootstrapPresent: boolean): void {
ordersGateway = jasmine.createSpyObj('AdminOrdersLocalGateway', ['loadOrders']);
productsGateway = jasmine.createSpyObj('AdminProductsLocalGateway', ['loadProducts']);
categoriesGateway = jasmine.createSpyObj('ADMIN_CATEGORIES_GATEWAY', ['loadCategories']);
moderationGateway = jasmine.createSpyObj('AdminModerationLocalGateway', ['loadReviews']);
dashboardFacade = jasmine.createSpyObj('AdminDashboardFacade', [
'ensureLoaded', 'activityEntries', 'bootstrap', 'validationIssues', 'enabledWidgetsCount', 'staticPagesUnpublishedCount',
]);
ordersGateway.loadOrders.and.returnValue(of({ items: [makeOrder()], total: 1 } as any));
productsGateway.loadProducts.and.returnValue(of({ items: [], total: 0 } as any));
categoriesGateway.loadCategories.and.returnValue(of([]));
moderationGateway.loadReviews.and.returnValue(of({ items: [], total: 0 } as any));
dashboardFacade.activityEntries.and.returnValue([]);
dashboardFacade.bootstrap.and.returnValue(bootstrapPresent ? ({ schemaVersion: '1', tenant: { id: 't1' } } as any) : null);
dashboardFacade.validationIssues.and.returnValue([]);
dashboardFacade.enabledWidgetsCount.and.returnValue(0);
dashboardFacade.staticPagesUnpublishedCount.and.returnValue(0);
TestBed.configureTestingModule({
providers: [
{ provide: AdminOrdersLocalGateway, useValue: ordersGateway },
{ provide: AdminProductsLocalGateway, useValue: productsGateway },
{ provide: ADMIN_CATEGORIES_GATEWAY, useValue: categoriesGateway },
{ provide: AdminModerationLocalGateway, useValue: moderationGateway },
{ provide: AdminDashboardFacade, useValue: dashboardFacade },
],
});
facade = TestBed.inject(AdminAnalyticsFacade);
}
it('never fabricates conversionRate - stays null even with real order data', () => {
configure(true);
facade.load();
expect(facade.summary()?.conversionRate).toBeNull();
expect(facade.summary()?.revenueTotal).toBe(100);
expect(facade.summary()?.ordersCount).toBe(1);
});
it('never fabricates the "performance" health check - always unknown (no real data source)', () => {
configure(true);
facade.load();
const performance = facade.marketplaceHealth().find(check => check.code === 'performance');
expect(performance?.status).toBe('unknown');
});
it('reports backend-connectivity and homepage-configured as unknown when bootstrap has not loaded, instead of guessing', () => {
configure(false);
facade.load();
const backend = facade.marketplaceHealth().find(check => check.code === 'backend-connectivity');
const homepage = facade.marketplaceHealth().find(check => check.code === 'homepage-configured');
expect(backend?.status).toBe('unknown');
expect(homepage?.status).toBe('unknown');
});
it('reports backend-connectivity as healthy only once bootstrap is actually present', () => {
configure(true);
facade.load();
const backend = facade.marketplaceHealth().find(check => check.code === 'backend-connectivity');
expect(backend?.status).toBe('healthy');
});
});