Files
opensourcepos/tests/Controllers/ItemKitsControllerTest.php
T
objecttothis 4bc2bac204 test: add export-all coverage across controllers and helpers
Add feature tests verifying CSV/PDF/XML/SQL export endpoints work
correctly across many controllers, plus helper unit tests and shared
fixtures to support them.

- Add downloadResponseBody() reflection helper (reads private $binary
  property on DownloadResponse) to CashupControllerTest,
  EmployeesControllerTest, ItemKitsControllerTest for asserting
  streamed download content
- Add exportAll CSV/PDF tests for Attributes, Customers,
  Expenses_categories, Expenses, Giftcards, ItemKits, Cashup,
  Employees controllers
- Add export_helperTest covering CSV sanitization (formula injection
  guard), JSON/XML/SQL writers, tag sanitization, and file generation
- Add Tests/Support/ExportFixtureTrait with reusable fixture objects
  for sales, employees, customers, suppliers, items, giftcards,
  item kits, attributes, expenses, cashups, tax rates
- Update AGENTS.md: clarify `use` import rule applies to every
  reference including global-namespace classes
  (ReflectionClass/ReflectionProperty/Exception) and method-signature
  type-hints, not just namespaced app classes

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
2026-10-21 15:47:09 +04:00

176 lines
5.7 KiB
PHP

<?php
namespace Tests\Controllers;
use CodeIgniter\Config\Factories;
use CodeIgniter\Database\Config;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;
use CodeIgniter\Test\FeatureTestTrait;
use App\Models\Item;
use App\Models\Item_kit;
use Config\OSPOS;
use Exception;
use ReflectionProperty;
class ItemKitsControllerTest extends CIUnitTestCase
{
use DatabaseTestTrait;
use FeatureTestTrait;
protected $migrate = true;
protected $migrateOnce = true;
protected $seedOnce = true;
protected $refresh = false;
protected $namespace = null;
private static bool $doneBootstrap = false;
protected Item $item;
protected Item_kit $itemKit;
protected function setUp(): void
{
if (self::$doneBootstrap === false) {
Config::seeder($this->DBGroup)->call('App\Database\Seeds\TestDatabaseBootstrapSeeder');
Config::connect($this->DBGroup)->close();
self::$doneBootstrap = true;
}
parent::setUp();
$ospos = new OSPOS();
$ospos->settings = [
'company' => 'Test Co',
'barcode_content' => 'id',
'barcode_type' => 'C128',
'barcode_font' => 'inconsolata.ttf',
'barcode_font_size' => 10,
'barcode_height' => 40,
'barcode_width' => 2,
'barcode_first_row' => 'item_code',
'barcode_second_row' => 'none',
'barcode_third_row' => 'none',
'barcode_num_in_row' => 1,
'barcode_page_width' => 8,
'barcode_page_cellspacing' => 1,
'barcode_generate_if_empty' => 0,
'barcode_formats' => 'null',
];
Factories::injectMock('config', OSPOS::class, $ospos);
$this->item = model(Item::class);
$this->itemKit = model(Item_kit::class);
}
protected function tearDown(): void
{
Factories::reset();
parent::tearDown();
}
protected function loginAsAdmin(): void
{
$this->withSession([
'person_id' => 1,
'menu_group' => 'office'
]);
}
// getBody() is empty for DownloadResponse; its content lives in the private $binary property.
protected function downloadResponseBody(ResponseInterface $response): string
{
$reflection = new ReflectionProperty($response, 'binary');
$reflection->setAccessible(true);
return (string) $reflection->getValue($response);
}
private function createItemKit(): int
{
$itemData = [
'item_id' => null,
'name' => 'Kit Base Item',
'category' => 'Test',
'cost_price' => 10.00,
'unit_price' => 20.00,
'deleted' => 0
];
$this->assertTrue($this->item->save_value($itemData));
$itemKitData = [
'name' => 'Test Kit',
'description' => 'Test Kit Description',
'item_id' => $itemData['item_id'],
'kit_discount' => 0,
'kit_discount_type' => 0,
'price_option' => 0,
'print_option' => 0
];
$this->assertTrue($this->itemKit->save_value($itemKitData));
return (int) $itemKitData['item_kit_id'];
}
/**
* @throws Exception
*/
public function testGenerateBarcodesDoesNotDecodeTripleEncodedPayload(): void
{
$itemKitId = $this->createItemKit();
$this->loginAsAdmin();
// <svg onload=alert(document.domain)> URL-encoded three times.
// The framework's router decodes this twice before routing; the controller used to apply
// a third urldecode(), turning the remaining %3C.../%3E into a live <svg onload=...> tag.
// With that urldecode() removed, the value must stay percent-encoded text and never
// become a raw '<' in the response.
$payload = $itemKitId . '%25253Csvg%252520onload%25253Dalert%252528document.domain%252529%25253E';
$response = $this->get('/item_kits/generateBarcodes/' . $payload);
$response->assertStatus(200);
$body = $response->getBody();
$this->assertStringNotContainsString('<svg onload', $body);
$this->assertStringContainsString('%3Csvg', $body);
}
public function testGenerateBarcodesWorksForPlainItemKitId(): void
{
$itemKitId = $this->createItemKit();
$this->loginAsAdmin();
$response = $this->get('/item_kits/generateBarcodes/' . $itemKitId);
$response->assertStatus(200);
$this->assertStringContainsString('KIT ' . $itemKitId, $response->getBody());
}
public function testGetExportAll_StreamsCsvDownload(): void
{
$this->createItemKit();
$this->loginAsAdmin();
$response = $this->get('/item_kits/exportall/csv');
$response->assertStatus(200);
$response->assertHeader('Content-Disposition');
$this->assertStringContainsString('attachment', $response->getHeaderLine('Content-Disposition'));
$this->assertStringContainsString('item_kits_', $response->getHeaderLine('Content-Disposition'));
$this->assertStringContainsString('Test Kit', $this->downloadResponseBody($response->response()));
}
public function testGetExportAll_StreamsPdfDownload(): void
{
$this->createItemKit();
$this->loginAsAdmin();
$response = $this->get('/item_kits/exportall/pdf');
$response->assertStatus(200);
$this->assertStringContainsString('application/pdf', $response->getHeaderLine('Content-Type'));
}
}