Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6df1b46
Start fixing existing unit tests first
WilcoLouwerse Sep 2, 2025
6390f1e
w.i.p. fixing existing unit tests (Some changes need review/revert)
WilcoLouwerse Sep 4, 2025
bc1393e
Make sure all existing unit tests work
WilcoLouwerse Sep 5, 2025
3706012
Removed all skipped tests & fixed some tests that got changed a lot
WilcoLouwerse Sep 5, 2025
ec711d7
put back SearchController tests
WilcoLouwerse Sep 5, 2025
882bca8
Added test coverage for all services
WilcoLouwerse Sep 9, 2025
0b65fa4
Added tests for a few more services + fixes
WilcoLouwerse Sep 9, 2025
1d0defa
Make sure all unit tests pass
WilcoLouwerse Sep 11, 2025
97160e4
Added unit tests for all Controllers
WilcoLouwerse Sep 12, 2025
4fb9fc3
w.i.p. on making sure all existing Unit tests pass
WilcoLouwerse Sep 15, 2025
59d9856
Some final fixes to make sure all existing Unit tests pass
WilcoLouwerse Sep 16, 2025
dd2d599
Fix function in SettingsService
WilcoLouwerse Sep 16, 2025
beefcaa
Some finishing touches for unit test (improved coverage and quality)
WilcoLouwerse Sep 18, 2025
a5e73b8
Merge branch 'development' into feature/REGISTERS-221/unit-tests
WilcoLouwerse Sep 18, 2025
21e71c4
Add / update unit tests with new changes from Development branch
WilcoLouwerse Sep 19, 2025
b610a81
Improved unit test coverage and quality + changes for new code from dev
WilcoLouwerse Sep 19, 2025
47b56f4
Fix mocking for ImportServiceTest
WilcoLouwerse Sep 19, 2025
fe8a89a
small fixes, more complex tests and started adding documentation
WilcoLouwerse Sep 19, 2025
d3da084
Added documentation for complex unit test files
WilcoLouwerse Sep 20, 2025
48b29ac
Merge branch 'development' into feature/REGISTERS-221/unit-tests
WilcoLouwerse Sep 22, 2025
89b4459
Some more fixes / changes for new development code
WilcoLouwerse Sep 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions lib/Controller/ObjectsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,9 @@ private function isCurrentUserAdmin(): bool
public function page(): TemplateResponse
{
return new TemplateResponse(
appName: 'openconnector',
templateName: 'index',
parameters: []
'openconnector',
'index',
[]
);

}//end page()
Expand Down Expand Up @@ -1219,7 +1219,7 @@ public function unlock(string $register, string $schema, string $id): JSONRespon
{
$this->objectService->setRegister($register);
$this->objectService->setSchema($schema);
$this->objectService->unlock($id);
$this->objectService->unlockObject($id);
return new JSONResponse(['message' => 'Object unlocked successfully']);

}//end unlock()
Expand Down
7 changes: 4 additions & 3 deletions lib/Controller/SchemasController.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public function __construct(
private readonly SchemaMapper $schemaMapper,
private readonly ObjectEntityMapper $objectEntityMapper,
private readonly DownloadService $downloadService,
private readonly ObjectService $objectService,
private readonly UploadService $uploadService,
private readonly AuditTrailMapper $auditTrailMapper,
private readonly OrganisationService $organisationService,
Expand All @@ -95,9 +96,9 @@ public function __construct(
public function page(): TemplateResponse
{
return new TemplateResponse(
appName: 'openconnector',
templateName: 'index',
parameters: []
'openconnector',
'index',
[]
);

}//end page()
Expand Down
36 changes: 22 additions & 14 deletions lib/Controller/SourcesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
use OCA\OpenRegister\Db\Source;
use OCA\OpenRegister\Db\SourceMapper;
use OCA\OpenRegister\Service\ObjectService;
use OCA\OpenRegister\Service\SearchService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\DB\Exception;
Expand Down Expand Up @@ -85,7 +85,6 @@ public function page(): TemplateResponse
* This method returns a JSON response containing an array of all sources in the system.
*
* @param ObjectService $objectService The object service
* @param SearchService $searchService The search service
*
* @return JSONResponse A JSON response containing the list of sources
*
Expand All @@ -94,20 +93,29 @@ public function page(): TemplateResponse
* @NoCSRFRequired
*/
public function index(
ObjectService $objectService,
SearchService $searchService
ObjectService $objectService
): JSONResponse {
// Get request parameters for filtering and searching.
$filters = $this->request->getParams();
$fieldsToSearch = ['title', 'description'];

// Create search parameters and conditions for filtering.
$searchParams = $searchService->createMySQLSearchParams(filters: $filters);
$searchConditions = $searchService->createMySQLSearchConditions(
filters: $filters,
fieldsToSearch: $fieldsToSearch
);
$filters = $searchService->unsetSpecialQueryParams(filters: $filters);
$filters = $this->request->getParams();

// Create simple search conditions for title and description fields
$searchConditions = [];
$searchParams = [];

if (isset($filters['search']) || isset($filters['q'])) {
$searchTerm = $filters['search'] ?? $filters['q'] ?? '';
if (!empty($searchTerm)) {
$searchConditions[] = '(title LIKE ? OR description LIKE ?)';
$searchParams[] = '%' . $searchTerm . '%';
$searchParams[] = '%' . $searchTerm . '%';
}
}

// Remove special query parameters that are not database fields
$specialParams = ['limit', 'offset', 'sort', 'order', 'search', 'q'];
foreach ($specialParams as $param) {
unset($filters[$param]);
}

// Return all sources that match the search conditions.
return new JSONResponse(
Expand Down
11 changes: 11 additions & 0 deletions lib/Db/Register.php
Original file line number Diff line number Diff line change
Expand Up @@ -377,5 +377,16 @@ public function __toString(): string

}//end __toString()

/**
* Get the unique identifier for the register
* Override parent method since this class uses 'uuid' instead of 'id'
*
* @return string|null The unique identifier
*/
public function getId(): ?string
{
return $this->uuid;
}//end getId()


}//end class
42 changes: 42 additions & 0 deletions lib/Db/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,18 @@ public function getIcon(): ?string
}//end getIcon()


/**
* Get the hard validation setting for the schema
*
* @return bool Whether hard validation is enabled
*/
public function getHardValidation(): bool
{
return $this->hardValidation;

}//end getHardValidation()


/**
* Set the icon for the schema
*
Expand Down Expand Up @@ -964,6 +976,36 @@ public function __toString(): string

}//end __toString()

/**
* Get the unique identifier for the schema
* Override parent method since this class uses 'uuid' instead of 'id'
*
* @return string|null The unique identifier
*/
public function getId(): ?string
{
return $this->uuid;
}//end getId()

/**
* Get the title of the schema
*
* @return string|null The schema title
*/
public function getTitle(): ?string
{
return $this->title;
}//end getTitle()

/**
* Get the slug of the schema
*
* @return string|null The schema slug
*/
public function getSlug(): ?string
{
return $this->slug;
}//end getSlug()

/**
* Get the pre-computed facet configuration
Expand Down
13 changes: 13 additions & 0 deletions lib/Db/SchemaMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -823,4 +823,17 @@ private function determineFacetTypeFromProperty(array $property): string
}//end determineFacetTypeFromProperty()


/**
* Get register count for a schema
*
* @param int $schemaId The schema ID
* @return int Register count
*/
public function getRegisterCount(int $schemaId): int
{
$counts = $this->getRegisterCountPerSchema();
return $counts[$schemaId] ?? 0;
}//end getRegisterCount()


}//end class
9 changes: 9 additions & 0 deletions lib/Exception/LockedException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace OCA\OpenRegister\Exception;

use Exception;

class LockedException extends Exception
{
}
28 changes: 22 additions & 6 deletions lib/Formats/BsnFormat.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,33 @@ class BsnFormat implements Format
*/
public function validate(mixed $data): bool
{
$data = str_pad(
string: $data,
length:9,
pad_string: "0",
pad_type: STR_PAD_LEFT,
);
if ($data === null || $data === '') {
return false;
}

// Only accept strings and numeric values that can be meaningfully converted
if (is_string($data) === false && is_numeric($data) === false) {
return false;
}

$dataString = (string) $data;

// Reject numbers that are not exactly 9 digits
if (strlen($dataString) !== 9) {
return false;
}

$data = $dataString;

if (ctype_digit($data) === false) {
return false;
}

// Reject all-zero BSNs (000000000)
if ($data === '000000000') {
return false;
}

$control = 0;
$reversedIterator = 9;
foreach (str_split($data) as $character) {
Expand Down
75 changes: 43 additions & 32 deletions lib/Service/ImportService.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use Psr\Log\LoggerInterface;
use React\Async\PromiseInterface;
use React\Promise\PromiseInterface;
use React\Promise\Promise;
use React\EventLoop\Loop;

Expand Down Expand Up @@ -618,38 +618,49 @@ private function processSpreadsheetBatch(
// Call saveObjects ONCE with all objects - NO ERROR SUPPRESSION!
// This will reveal the real bulk save problem immediately
if (!empty($allObjects) && $register !== null && $schema !== null) {
// Add publish date to all objects if publish is enabled
if ($publish) {
$publishDate = (new \DateTime())->format('c'); // ISO 8601 format
$allObjects = $this->addPublishedDateToObjects($allObjects, $publishDate);
}

$saveResult = $this->objectService->saveObjects($allObjects, $register, $schema, $rbac, $multi, $validation, $events);

// Use the structured return from saveObjects with smart deduplication
// saveObjects returns ObjectEntity->jsonSerialize() arrays where UUID is in @self.id
$summary['created'] = array_map(fn($obj) => $obj['@self']['id'] ?? $obj['uuid'] ?? $obj['id'] ?? null, $saveResult['saved'] ?? []);
$summary['updated'] = array_map(fn($obj) => $obj['@self']['id'] ?? $obj['uuid'] ?? $obj['id'] ?? null, $saveResult['updated'] ?? []);

// TODO: Handle unchanged objects from smart deduplication (renamed from 'skipped')
$summary['unchanged'] = array_map(fn($obj) => $obj['@self']['id'] ?? $obj['uuid'] ?? $obj['id'] ?? null, $saveResult['unchanged'] ?? []);

// Add efficiency metrics from smart deduplication
$totalProcessed = count($summary['created']) + count($summary['updated']) + count($summary['unchanged']);
if ($totalProcessed > 0 && count($summary['unchanged']) > 0) {
$summary['deduplication_efficiency'] = round((count($summary['unchanged']) / $totalProcessed) * 100, 1) . '% operations avoided';
}

// Handle validation errors if validation was enabled
if ($validation && !empty($saveResult['invalid'] ?? [])) {
foreach (($saveResult['invalid'] ?? []) as $invalidItem) {
$summary['errors'][] = [
'sheet' => $sheetTitle,
'object' => $invalidItem['object'] ?? $invalidItem,
'error' => $invalidItem['error'] ?? 'Validation failed',
'type' => $invalidItem['type'] ?? 'ValidationException',
];
try {
// Add publish date to all objects if publish is enabled
if ($publish) {
$publishDate = (new \DateTime())->format('c'); // ISO 8601 format
$allObjects = $this->addPublishedDateToObjects($allObjects, $publishDate);
}

$saveResult = $this->objectService->saveObjects($allObjects, $register, $schema, $rbac, $multi, $validation, $events);

// Use the structured return from saveObjects with smart deduplication
// saveObjects returns ObjectEntity->jsonSerialize() arrays where UUID is in @self.id
$summary['created'] = array_map(fn($obj) => $obj['@self']['id'] ?? $obj['uuid'] ?? $obj['id'] ?? null, $saveResult['saved'] ?? []);
$summary['updated'] = array_map(fn($obj) => $obj['@self']['id'] ?? $obj['uuid'] ?? $obj['id'] ?? null, $saveResult['updated'] ?? []);

// TODO: Handle unchanged objects from smart deduplication (renamed from 'skipped')
$summary['unchanged'] = array_map(fn($obj) => $obj['@self']['id'] ?? $obj['uuid'] ?? $obj['id'] ?? null, $saveResult['unchanged'] ?? []);

// Add efficiency metrics from smart deduplication
$totalProcessed = count($summary['created']) + count($summary['updated']) + count($summary['unchanged']);
if ($totalProcessed > 0 && count($summary['unchanged']) > 0) {
$summary['deduplication_efficiency'] = round((count($summary['unchanged']) / $totalProcessed) * 100, 1) . '% operations avoided';
}

// Handle validation errors if validation was enabled
if ($validation && !empty($saveResult['invalid'] ?? [])) {
foreach (($saveResult['invalid'] ?? []) as $invalidItem) {
$summary['errors'][] = [
'sheet' => $sheetTitle,
'object' => $invalidItem['object'] ?? $invalidItem,
'error' => $invalidItem['error'] ?? 'Validation failed',
'type' => $invalidItem['type'] ?? 'ValidationException',
];
}
}
} catch (\Exception $e) {
// Handle batch save errors
$summary['errors'][] = [
'sheet' => $sheetTitle,
'row' => 'batch',
'object' => [],
'error' => 'Batch save failed: ' . $e->getMessage(),
'type' => 'BatchSaveException',
];
}
}

Expand Down
Loading
Loading