mirror of
https://github.com/chenasraf/nextcloud-forum.git
synced 2026-05-18 01:28:58 +00:00
feat: user signatures
This commit is contained in:
@@ -56,7 +56,8 @@ class UserPreferencesController extends OCSController {
|
||||
/**
|
||||
* Update user preferences
|
||||
*
|
||||
* @param array<string, mixed> $preferences Key-value pairs of preferences to update
|
||||
* Request body should contain key-value pairs of preferences to update
|
||||
*
|
||||
* @return DataResponse<Http::STATUS_OK, array<string, mixed>, array{}>|DataResponse<Http::STATUS_UNAUTHORIZED, array{error: string}, array{}>|DataResponse<Http::STATUS_BAD_REQUEST, array{error: string}, array{}>
|
||||
*
|
||||
* 200: Preferences updated
|
||||
@@ -65,13 +66,18 @@ class UserPreferencesController extends OCSController {
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[ApiRoute(verb: 'PUT', url: '/api/user-preferences')]
|
||||
public function update(array $preferences): DataResponse {
|
||||
public function update(): DataResponse {
|
||||
try {
|
||||
$user = $this->userSession->getUser();
|
||||
if (!$user) {
|
||||
return new DataResponse(['error' => 'User not authenticated'], Http::STATUS_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
// Get preferences directly from request body
|
||||
$preferences = $this->request->getParams();
|
||||
// Remove route-specific params that Nextcloud adds
|
||||
unset($preferences['_route']);
|
||||
|
||||
$allPreferences = $this->preferencesService->updatePreferences($user->getUID(), $preferences);
|
||||
return new DataResponse($allPreferences);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
|
||||
@@ -23,6 +23,8 @@ use OCP\AppFramework\Db\Entity;
|
||||
* @method void setLastPostAt(?int $lastPostAt)
|
||||
* @method int|null getDeletedAt()
|
||||
* @method void setDeletedAt(?int $deletedAt)
|
||||
* @method string|null getSignature()
|
||||
* @method void setSignature(?string $signature)
|
||||
* @method int getCreatedAt()
|
||||
* @method void setCreatedAt(int $createdAt)
|
||||
* @method int getUpdatedAt()
|
||||
@@ -35,6 +37,7 @@ class UserStats extends Entity implements JsonSerializable {
|
||||
protected int $threadCount = 0;
|
||||
protected ?int $lastPostAt = null;
|
||||
protected ?int $deletedAt = null;
|
||||
protected ?string $signature = null;
|
||||
protected int $createdAt = 0;
|
||||
protected int $updatedAt = 0;
|
||||
|
||||
@@ -45,6 +48,7 @@ class UserStats extends Entity implements JsonSerializable {
|
||||
$this->addType('threadCount', 'integer');
|
||||
$this->addType('lastPostAt', 'integer');
|
||||
$this->addType('deletedAt', 'integer');
|
||||
$this->addType('signature', 'string');
|
||||
$this->addType('createdAt', 'integer');
|
||||
$this->addType('updatedAt', 'integer');
|
||||
}
|
||||
@@ -57,6 +61,7 @@ class UserStats extends Entity implements JsonSerializable {
|
||||
'lastPostAt' => $this->lastPostAt,
|
||||
'deletedAt' => $this->deletedAt,
|
||||
'isDeleted' => $this->deletedAt !== null,
|
||||
'signature' => $this->signature,
|
||||
'createdAt' => $this->createdAt,
|
||||
'updatedAt' => $this->updatedAt,
|
||||
];
|
||||
|
||||
@@ -52,6 +52,24 @@ class UserStatsMapper extends QBMapper {
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find user stats by multiple user IDs
|
||||
*
|
||||
* @param array<string> $userIds
|
||||
* @return array<UserStats>
|
||||
*/
|
||||
public function findByUserIds(array $userIds): array {
|
||||
if (empty($userIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->where($qb->expr()->in('user_id', $qb->createNamedParameter($userIds, IQueryBuilder::PARAM_STR_ARRAY)));
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update user stats (upsert pattern)
|
||||
* This is used when we need to ensure stats exist for a user
|
||||
|
||||
@@ -13,10 +13,9 @@ use OCP\Migration\IOutput;
|
||||
use OCP\Migration\SimpleMigrationStep;
|
||||
|
||||
/**
|
||||
* Remove slug column from forum_posts table
|
||||
*
|
||||
* Post slugs were never used and are unnecessary - posts are always
|
||||
* accessed by ID within the context of a thread.
|
||||
* Version 8 Migration:
|
||||
* - Remove slug column from forum_posts table (never used)
|
||||
* - Add signature column to forum_user_stats table
|
||||
*/
|
||||
class Version8Date20251128000000 extends SimpleMigrationStep {
|
||||
/**
|
||||
@@ -44,6 +43,18 @@ class Version8Date20251128000000 extends SimpleMigrationStep {
|
||||
}
|
||||
}
|
||||
|
||||
// Add signature column to user stats
|
||||
if ($schema->hasTable('forum_user_stats')) {
|
||||
$table = $schema->getTable('forum_user_stats');
|
||||
|
||||
if (!$table->hasColumn('signature')) {
|
||||
$table->addColumn('signature', 'text', [
|
||||
'notnull' => false,
|
||||
'default' => null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ declare(strict_types=1);
|
||||
namespace OCA\Forum\Service;
|
||||
|
||||
use OCA\Forum\AppInfo\Application;
|
||||
use OCA\Forum\Db\UserStatsMapper;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\IConfig;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
@@ -18,20 +20,31 @@ class UserPreferencesService {
|
||||
/** Preference key for upload directory path */
|
||||
public const PREF_UPLOAD_DIRECTORY = 'upload_directory';
|
||||
|
||||
/** Preference key for user signature (stored in user_stats table) */
|
||||
public const PREF_SIGNATURE = 'signature';
|
||||
|
||||
/** @var array<string, mixed> Default preference values */
|
||||
private const DEFAULTS = [
|
||||
self::PREF_AUTO_SUBSCRIBE_CREATED_THREADS => true,
|
||||
self::PREF_UPLOAD_DIRECTORY => 'Forum',
|
||||
self::PREF_SIGNATURE => '',
|
||||
];
|
||||
|
||||
/** @var array<string> List of valid preference keys */
|
||||
private const VALID_KEYS = [
|
||||
self::PREF_AUTO_SUBSCRIBE_CREATED_THREADS,
|
||||
self::PREF_UPLOAD_DIRECTORY,
|
||||
self::PREF_SIGNATURE,
|
||||
];
|
||||
|
||||
/** @var array<string> Keys stored in user_stats table instead of config */
|
||||
private const USER_STATS_KEYS = [
|
||||
self::PREF_SIGNATURE,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private IConfig $config,
|
||||
private UserStatsMapper $userStatsMapper,
|
||||
private LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
@@ -65,6 +78,11 @@ class UserPreferencesService {
|
||||
throw new \InvalidArgumentException("Invalid preference key: $key");
|
||||
}
|
||||
|
||||
// Handle keys stored in user_stats table
|
||||
if (in_array($key, self::USER_STATS_KEYS, true)) {
|
||||
return $this->getUserStatsValue($userId, $key);
|
||||
}
|
||||
|
||||
$default = self::DEFAULTS[$key] ?? null;
|
||||
$value = $this->config->getUserValue($userId, Application::APP_ID, $key, $default);
|
||||
|
||||
@@ -109,6 +127,12 @@ class UserPreferencesService {
|
||||
throw new \InvalidArgumentException("Invalid preference key: $key");
|
||||
}
|
||||
|
||||
// Handle keys stored in user_stats table
|
||||
if (in_array($key, self::USER_STATS_KEYS, true)) {
|
||||
$this->setUserStatsValue($userId, $key, $value);
|
||||
return;
|
||||
}
|
||||
|
||||
$stringValue = $this->stringifyValue($value);
|
||||
$this->config->setUserValue($userId, Application::APP_ID, $key, $stringValue);
|
||||
}
|
||||
@@ -144,4 +168,42 @@ class UserPreferencesService {
|
||||
}
|
||||
return (string)$value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value from user_stats table
|
||||
*
|
||||
* @param string $userId The user ID
|
||||
* @param string $key The preference key
|
||||
* @return mixed The value
|
||||
*/
|
||||
private function getUserStatsValue(string $userId, string $key): mixed {
|
||||
try {
|
||||
$stats = $this->userStatsMapper->find($userId);
|
||||
return match ($key) {
|
||||
self::PREF_SIGNATURE => $stats->getSignature() ?? '',
|
||||
default => self::DEFAULTS[$key] ?? null,
|
||||
};
|
||||
} catch (DoesNotExistException $e) {
|
||||
return self::DEFAULTS[$key] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value in user_stats table
|
||||
*
|
||||
* @param string $userId The user ID
|
||||
* @param string $key The preference key
|
||||
* @param mixed $value The value to set
|
||||
*/
|
||||
private function setUserStatsValue(string $userId, string $key, mixed $value): void {
|
||||
$stats = $this->userStatsMapper->createOrUpdate($userId);
|
||||
|
||||
match ($key) {
|
||||
self::PREF_SIGNATURE => $stats->setSignature((string)$value),
|
||||
default => null,
|
||||
};
|
||||
|
||||
$stats->setUpdatedAt(time());
|
||||
$this->userStatsMapper->update($stats);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace OCA\Forum\Service;
|
||||
|
||||
use OCA\Forum\Db\BBCodeMapper;
|
||||
use OCA\Forum\Db\RoleMapper;
|
||||
use OCA\Forum\Db\UserRoleMapper;
|
||||
use OCA\Forum\Db\UserStatsMapper;
|
||||
@@ -24,6 +25,8 @@ class UserService {
|
||||
private UserStatsMapper $userStatsMapper,
|
||||
private RoleMapper $roleMapper,
|
||||
private UserRoleMapper $userRoleMapper,
|
||||
private BBCodeMapper $bbCodeMapper,
|
||||
private BBCodeService $bbCodeService,
|
||||
private IL10N $l10n,
|
||||
) {
|
||||
}
|
||||
@@ -64,13 +67,14 @@ class UserService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Enrich user data with display name, deleted status, and roles
|
||||
* Enrich user data with display name, deleted status, roles, and signature
|
||||
*
|
||||
* @param string $userId
|
||||
* @param array|null $roles Optional pre-fetched roles array
|
||||
* @return array{userId: string, displayName: string, isDeleted: bool, roles: array}
|
||||
* @param array|null $bbcodes Optional pre-fetched BBCode definitions for parsing signatures
|
||||
* @return array{userId: string, displayName: string, isDeleted: bool, roles: array, signature: ?string, signatureRaw: ?string}
|
||||
*/
|
||||
public function enrichUserData(string $userId, ?array $roles = null): array {
|
||||
public function enrichUserData(string $userId, ?array $roles = null, ?array $bbcodes = null): array {
|
||||
$isDeleted = $this->isUserDeleted($userId);
|
||||
$displayName = $this->getUserDisplayName($userId);
|
||||
|
||||
@@ -88,11 +92,30 @@ class UserService {
|
||||
}
|
||||
}
|
||||
|
||||
// Get signature from user stats
|
||||
$signatureRaw = null;
|
||||
$signature = null;
|
||||
try {
|
||||
$stats = $this->userStatsMapper->find($userId);
|
||||
$signatureRaw = $stats->getSignature();
|
||||
if ($signatureRaw !== null && $signatureRaw !== '') {
|
||||
// Parse BBCode in signature
|
||||
if ($bbcodes === null) {
|
||||
$bbcodes = $this->bbCodeMapper->findAllEnabled();
|
||||
}
|
||||
$signature = $this->bbCodeService->parse($signatureRaw, $bbcodes);
|
||||
}
|
||||
} catch (DoesNotExistException $e) {
|
||||
// No stats record, no signature
|
||||
}
|
||||
|
||||
return [
|
||||
'userId' => $userId,
|
||||
'displayName' => $displayName,
|
||||
'isDeleted' => $isDeleted,
|
||||
'roles' => $roles,
|
||||
'signature' => $signature,
|
||||
'signatureRaw' => $signatureRaw,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -101,9 +124,10 @@ class UserService {
|
||||
*
|
||||
* @param array<string> $userIds
|
||||
* @param array<string, array> $rolesMap Optional pre-fetched roles map (userId => roles[])
|
||||
* @return array<string, array{userId: string, displayName: string, isDeleted: bool, roles: array}>
|
||||
* @param array|null $bbcodes Optional pre-fetched BBCode definitions for parsing signatures
|
||||
* @return array<string, array{userId: string, displayName: string, isDeleted: bool, roles: array, signature: ?string, signatureRaw: ?string}>
|
||||
*/
|
||||
public function enrichMultipleUsers(array $userIds, ?array $rolesMap = null): array {
|
||||
public function enrichMultipleUsers(array $userIds, ?array $rolesMap = null, ?array $bbcodes = null): array {
|
||||
$result = [];
|
||||
|
||||
// If roles not provided, fetch them all at once
|
||||
@@ -111,15 +135,31 @@ class UserService {
|
||||
$rolesMap = $this->fetchRolesForUsers($userIds);
|
||||
}
|
||||
|
||||
// Fetch all user stats at once for signatures
|
||||
$signaturesMap = $this->fetchSignaturesForUsers($userIds);
|
||||
|
||||
// Fetch BBCodes once for parsing all signatures (if not provided)
|
||||
if ($bbcodes === null) {
|
||||
$bbcodes = $this->bbCodeMapper->findAllEnabled();
|
||||
}
|
||||
|
||||
foreach ($userIds as $userId) {
|
||||
$isDeleted = $this->isUserDeleted($userId);
|
||||
$displayName = $this->getUserDisplayName($userId);
|
||||
|
||||
$signatureRaw = $signaturesMap[$userId] ?? null;
|
||||
$signature = null;
|
||||
if ($signatureRaw !== null && $signatureRaw !== '') {
|
||||
$signature = $this->bbCodeService->parse($signatureRaw, $bbcodes);
|
||||
}
|
||||
|
||||
$result[$userId] = [
|
||||
'userId' => $userId,
|
||||
'displayName' => $displayName,
|
||||
'isDeleted' => $isDeleted,
|
||||
'roles' => $rolesMap[$userId] ?? [],
|
||||
'signature' => $signature,
|
||||
'signatureRaw' => $signatureRaw,
|
||||
];
|
||||
}
|
||||
return $result;
|
||||
@@ -178,4 +218,33 @@ class UserService {
|
||||
|
||||
return $rolesMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch signatures for multiple users efficiently
|
||||
*
|
||||
* @param array<string> $userIds
|
||||
* @return array<string, ?string> Map of userId => signature (raw)
|
||||
*/
|
||||
private function fetchSignaturesForUsers(array $userIds): array {
|
||||
if (empty($userIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$signaturesMap = [];
|
||||
|
||||
// Initialize all user IDs with null
|
||||
foreach ($userIds as $userId) {
|
||||
$signaturesMap[$userId] = null;
|
||||
}
|
||||
|
||||
// Fetch all user stats for these users
|
||||
$userStats = $this->userStatsMapper->findByUserIds($userIds);
|
||||
|
||||
// Extract signatures
|
||||
foreach ($userStats as $stats) {
|
||||
$signaturesMap[$stats->getUserId()] = $stats->getSignature();
|
||||
}
|
||||
|
||||
return $signaturesMap;
|
||||
}
|
||||
}
|
||||
|
||||
23
openapi.json
23
openapi.json
@@ -8283,6 +8283,7 @@
|
||||
"put": {
|
||||
"operationId": "user_preferences-update",
|
||||
"summary": "Update user preferences",
|
||||
"description": "Request body should contain key-value pairs of preferences to update",
|
||||
"tags": [
|
||||
"user_preferences"
|
||||
],
|
||||
@@ -8294,28 +8295,6 @@
|
||||
"basic_auth": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"preferences"
|
||||
],
|
||||
"properties": {
|
||||
"preferences": {
|
||||
"type": "object",
|
||||
"description": "Key-value pairs of preferences to update",
|
||||
"additionalProperties": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "OCS-APIRequest",
|
||||
|
||||
@@ -59,6 +59,11 @@
|
||||
<div v-else class="content-text" v-html="formattedContent"></div>
|
||||
</div>
|
||||
|
||||
<!-- Signature -->
|
||||
<div v-if="hasSignature && !isEditing" class="post-signature">
|
||||
<div class="signature-content" v-html="post.author?.signature"></div>
|
||||
</div>
|
||||
|
||||
<!-- Reactions (hidden when editing) -->
|
||||
<PostReactions
|
||||
v-if="!isEditing"
|
||||
@@ -179,6 +184,9 @@ export default defineComponent({
|
||||
// BBCodeService handles HTML escaping before parsing BBCodes
|
||||
return this.post.content
|
||||
},
|
||||
hasSignature(): boolean {
|
||||
return !!this.post.author?.signature
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
closeActionsMenu() {
|
||||
@@ -339,7 +347,14 @@ export default defineComponent({
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.content-text {
|
||||
.post-signature {
|
||||
margin-top: 24px;
|
||||
padding-top: 24px;
|
||||
border-top: 1px dashed var(--color-border-dark);
|
||||
}
|
||||
|
||||
.content-text,
|
||||
.signature-content {
|
||||
color: var(--color-main-text);
|
||||
line-height: 1.6;
|
||||
font-size: 0.95rem;
|
||||
|
||||
@@ -30,6 +30,8 @@ export interface User {
|
||||
displayName: string
|
||||
isDeleted: boolean
|
||||
roles: Role[]
|
||||
signature: string | null
|
||||
signatureRaw: string | null
|
||||
}
|
||||
|
||||
export interface Thread {
|
||||
|
||||
@@ -73,6 +73,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Signature Section -->
|
||||
<div class="form-section">
|
||||
<h3>{{ strings.signatureTitle }}</h3>
|
||||
<p class="section-description muted">{{ strings.signatureDesc }}</p>
|
||||
|
||||
<div class="preference-item">
|
||||
<label class="preference-label">{{ strings.signatureLabel }}</label>
|
||||
<BBCodeEditor
|
||||
v-model="formData.signature"
|
||||
:placeholder="strings.signaturePlaceholder"
|
||||
:rows="3"
|
||||
min-height="5rem"
|
||||
/>
|
||||
<p class="preference-hint">{{ strings.signatureHint }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="form-actions">
|
||||
<NcButton :disabled="saving || !hasChanges" @click="resetForm">
|
||||
@@ -107,6 +124,7 @@ import NcTextField from '@nextcloud/vue/components/NcTextField'
|
||||
import AppToolbar from '@/components/AppToolbar.vue'
|
||||
import PageWrapper from '@/components/PageWrapper.vue'
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
import BBCodeEditor from '@/components/BBCodeEditor.vue'
|
||||
import ArrowLeftIcon from '@icons/ArrowLeft.vue'
|
||||
import CheckIcon from '@icons/Check.vue'
|
||||
import FolderIcon from '@icons/Folder.vue'
|
||||
@@ -117,6 +135,7 @@ import { getFilePickerBuilder, FilePickerType } from '@nextcloud/dialogs'
|
||||
interface UserPreferences {
|
||||
auto_subscribe_created_threads: boolean
|
||||
upload_directory: string
|
||||
signature: string
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
@@ -130,6 +149,7 @@ export default defineComponent({
|
||||
AppToolbar,
|
||||
PageWrapper,
|
||||
PageHeader,
|
||||
BBCodeEditor,
|
||||
ArrowLeftIcon,
|
||||
CheckIcon,
|
||||
FolderIcon,
|
||||
@@ -143,10 +163,12 @@ export default defineComponent({
|
||||
originalData: {
|
||||
auto_subscribe_created_threads: true,
|
||||
upload_directory: 'Forum',
|
||||
signature: '',
|
||||
} as UserPreferences,
|
||||
formData: {
|
||||
auto_subscribe_created_threads: true,
|
||||
upload_directory: 'Forum',
|
||||
signature: '',
|
||||
} as UserPreferences,
|
||||
|
||||
strings: {
|
||||
@@ -174,6 +196,11 @@ export default defineComponent({
|
||||
save: t('forum', 'Save'),
|
||||
cancel: t('forum', 'Cancel'),
|
||||
saveSuccess: t('forum', 'Preferences saved'),
|
||||
signatureTitle: t('forum', 'Signature'),
|
||||
signatureDesc: t('forum', 'Your signature appears at the bottom of your posts'),
|
||||
signatureLabel: t('forum', 'Signature'),
|
||||
signatureHint: t('forum', 'You can use BBCode formatting in your signature'),
|
||||
signaturePlaceholder: t('forum', 'Enter your signature …'),
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -182,7 +209,8 @@ export default defineComponent({
|
||||
return (
|
||||
this.formData.auto_subscribe_created_threads !==
|
||||
this.originalData.auto_subscribe_created_threads ||
|
||||
this.formData.upload_directory !== this.originalData.upload_directory
|
||||
this.formData.upload_directory !== this.originalData.upload_directory ||
|
||||
this.formData.signature !== this.originalData.signature
|
||||
)
|
||||
},
|
||||
},
|
||||
|
||||
@@ -5,7 +5,9 @@ declare(strict_types=1);
|
||||
namespace OCA\Forum\Tests\Service;
|
||||
|
||||
use OCA\Forum\AppInfo\Application;
|
||||
use OCA\Forum\Db\UserStatsMapper;
|
||||
use OCA\Forum\Service\UserPreferencesService;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\IConfig;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -13,14 +15,21 @@ use Psr\Log\LoggerInterface;
|
||||
class UserPreferencesServiceTest extends TestCase {
|
||||
private UserPreferencesService $service;
|
||||
private IConfig $config;
|
||||
private UserStatsMapper $userStatsMapper;
|
||||
private LoggerInterface $logger;
|
||||
|
||||
protected function setUp(): void {
|
||||
$this->config = $this->createMock(IConfig::class);
|
||||
$this->userStatsMapper = $this->createMock(UserStatsMapper::class);
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
|
||||
// By default, mock no user stats (no signature)
|
||||
$this->userStatsMapper->method('find')
|
||||
->willThrowException(new DoesNotExistException(''));
|
||||
|
||||
$this->service = new UserPreferencesService(
|
||||
$this->config,
|
||||
$this->userStatsMapper,
|
||||
$this->logger
|
||||
);
|
||||
}
|
||||
@@ -28,6 +37,7 @@ class UserPreferencesServiceTest extends TestCase {
|
||||
public function testGetAllPreferencesReturnsAllPreferences(): void {
|
||||
$userId = 'user1';
|
||||
|
||||
// Only config-based preferences (signature is from user_stats)
|
||||
$this->config->expects($this->exactly(2))
|
||||
->method('getUserValue')
|
||||
->willReturnCallback(function ($uid, $appId, $key, $default) use ($userId) {
|
||||
@@ -44,9 +54,10 @@ class UserPreferencesServiceTest extends TestCase {
|
||||
$result = $this->service->getAllPreferences($userId);
|
||||
|
||||
$this->assertIsArray($result);
|
||||
$this->assertCount(2, $result);
|
||||
$this->assertCount(3, $result);
|
||||
$this->assertTrue($result[UserPreferencesService::PREF_AUTO_SUBSCRIBE_CREATED_THREADS]);
|
||||
$this->assertEquals('Forum', $result[UserPreferencesService::PREF_UPLOAD_DIRECTORY]);
|
||||
$this->assertEquals('', $result[UserPreferencesService::PREF_SIGNATURE]);
|
||||
}
|
||||
|
||||
public function testGetPreferenceReturnsCorrectValue(): void {
|
||||
@@ -145,8 +156,10 @@ class UserPreferencesServiceTest extends TestCase {
|
||||
$result = $this->service->updatePreferences($userId, $preferences);
|
||||
|
||||
$this->assertIsArray($result);
|
||||
$this->assertCount(3, $result);
|
||||
$this->assertFalse($result[UserPreferencesService::PREF_AUTO_SUBSCRIBE_CREATED_THREADS]);
|
||||
$this->assertEquals('Documents', $result[UserPreferencesService::PREF_UPLOAD_DIRECTORY]);
|
||||
$this->assertEquals('', $result[UserPreferencesService::PREF_SIGNATURE]);
|
||||
}
|
||||
|
||||
public function testUpdatePreferencesThrowsExceptionForInvalidKey(): void {
|
||||
|
||||
Reference in New Issue
Block a user