Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117741480
D3614.1775164150.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
62 KB
Referenced Files
None
Subscribers
None
D3614.1775164150.diff
View Options
diff --git a/src/app/Handlers/DomainHosting.php b/src/app/Handlers/DomainHosting.php
--- a/src/app/Handlers/DomainHosting.php
+++ b/src/app/Handlers/DomainHosting.php
@@ -13,4 +13,21 @@
{
return \App\Domain::class;
}
+
+ /**
+ * SKU handler metadata.
+ *
+ * @param \App\Sku $sku The SKU object
+ *
+ * @return array
+ */
+ public static function metadata(\App\Sku $sku): array
+ {
+ $data = parent::metadata($sku);
+
+ $data['readonly'] = true;
+ $data['enabled'] = true;
+
+ return $data;
+ }
}
diff --git a/src/app/Handlers/Group.php b/src/app/Handlers/Group.php
--- a/src/app/Handlers/Group.php
+++ b/src/app/Handlers/Group.php
@@ -13,4 +13,21 @@
{
return \App\Group::class;
}
+
+ /**
+ * SKU handler metadata.
+ *
+ * @param \App\Sku $sku The SKU object
+ *
+ * @return array
+ */
+ public static function metadata(\App\Sku $sku): array
+ {
+ $data = parent::metadata($sku);
+
+ $data['readonly'] = true;
+ $data['enabled'] = true;
+
+ return $data;
+ }
}
diff --git a/src/app/Handlers/Resource.php b/src/app/Handlers/Resource.php
--- a/src/app/Handlers/Resource.php
+++ b/src/app/Handlers/Resource.php
@@ -13,4 +13,21 @@
{
return \App\Resource::class;
}
+
+ /**
+ * SKU handler metadata.
+ *
+ * @param \App\Sku $sku The SKU object
+ *
+ * @return array
+ */
+ public static function metadata(\App\Sku $sku): array
+ {
+ $data = parent::metadata($sku);
+
+ $data['readonly'] = true;
+ $data['enabled'] = true;
+
+ return $data;
+ }
}
diff --git a/src/app/Handlers/SharedFolder.php b/src/app/Handlers/SharedFolder.php
--- a/src/app/Handlers/SharedFolder.php
+++ b/src/app/Handlers/SharedFolder.php
@@ -13,4 +13,21 @@
{
return \App\SharedFolder::class;
}
+
+ /**
+ * SKU handler metadata.
+ *
+ * @param \App\Sku $sku The SKU object
+ *
+ * @return array
+ */
+ public static function metadata(\App\Sku $sku): array
+ {
+ $data = parent::metadata($sku);
+
+ $data['readonly'] = true;
+ $data['enabled'] = true;
+
+ return $data;
+ }
}
diff --git a/src/app/Http/Controllers/API/V4/DomainsController.php b/src/app/Http/Controllers/API/V4/DomainsController.php
--- a/src/app/Http/Controllers/API/V4/DomainsController.php
+++ b/src/app/Http/Controllers/API/V4/DomainsController.php
@@ -205,16 +205,8 @@
// Status info
$response['statusInfo'] = self::statusInfo($domain);
- // Entitlements info
- $response['skus'] = \App\Entitlement::objectEntitlementsSummary($domain);
-
- // Some basic information about the domain wallet
- $wallet = $domain->wallet();
- $response['wallet'] = $wallet->toArray();
- if ($wallet->discount) {
- $response['wallet']['discount'] = $wallet->discount->discount;
- $response['wallet']['discount_description'] = $wallet->discount->description;
- }
+ // Entitlements/Wallet info
+ SkusController::objectEntitlements($domain, $response);
return response()->json($response);
}
diff --git a/src/app/Http/Controllers/API/V4/GroupsController.php b/src/app/Http/Controllers/API/V4/GroupsController.php
--- a/src/app/Http/Controllers/API/V4/GroupsController.php
+++ b/src/app/Http/Controllers/API/V4/GroupsController.php
@@ -184,6 +184,8 @@
return response()->json(['status' => 'error', 'errors' => $errors], 422);
}
+ // SkusController::updateEntitlements($group, $request->skus);
+
$group->members = $members;
$group->save();
diff --git a/src/app/Http/Controllers/API/V4/ResourcesController.php b/src/app/Http/Controllers/API/V4/ResourcesController.php
--- a/src/app/Http/Controllers/API/V4/ResourcesController.php
+++ b/src/app/Http/Controllers/API/V4/ResourcesController.php
@@ -132,6 +132,8 @@
return response()->json(['status' => 'error', 'errors' => $errors], 422);
}
+ // SkusController::updateEntitlements($resource, $request->skus);
+
$resource->save();
return response()->json([
diff --git a/src/app/Http/Controllers/API/V4/SharedFoldersController.php b/src/app/Http/Controllers/API/V4/SharedFoldersController.php
--- a/src/app/Http/Controllers/API/V4/SharedFoldersController.php
+++ b/src/app/Http/Controllers/API/V4/SharedFoldersController.php
@@ -118,6 +118,8 @@
DB::beginTransaction();
+ // SkusController::updateEntitlements($folder, $request->skus);
+
if ($name && $name != $folder->name) {
$folder->name = $name;
}
diff --git a/src/app/Http/Controllers/API/V4/SkusController.php b/src/app/Http/Controllers/API/V4/SkusController.php
--- a/src/app/Http/Controllers/API/V4/SkusController.php
+++ b/src/app/Http/Controllers/API/V4/SkusController.php
@@ -9,72 +9,45 @@
class SkusController extends ResourceController
{
/**
- * Get a list of SKUs available to the domain.
- *
- * @param int $id Domain identifier
- *
- * @return \Illuminate\Http\JsonResponse
- */
- public function domainSkus($id)
- {
- $domain = \App\Domain::find($id);
-
- if (!$this->checkTenant($domain)) {
- return $this->errorResponse(404);
- }
-
- if (!$this->guard()->user()->canRead($domain)) {
- return $this->errorResponse(403);
- }
-
- return $this->objectSkus($domain);
- }
-
- /**
* Get a list of active SKUs.
*
* @return \Illuminate\Http\JsonResponse
*/
public function index()
{
- // Note: Order by title for consistent ordering in tests
- $skus = Sku::withSubjectTenantContext()->where('active', true)->orderBy('title')->get();
-
- $response = [];
-
- foreach ($skus as $sku) {
- if ($data = $this->skuElement($sku)) {
- $response[] = $data;
- }
- }
-
- usort($response, function ($a, $b) {
- return ($b['prio'] <=> $a['prio']);
- });
-
- return response()->json($response);
- }
-
- /**
- * Get a list of SKUs available to the user.
- *
- * @param int $id User identifier
- *
- * @return \Illuminate\Http\JsonResponse
- */
- public function userSkus($id)
- {
- $user = \App\User::find($id);
+ $type = request()->input('type');
- if (!$this->checkTenant($user)) {
- return $this->errorResponse(404);
- }
+ // Note: Order by title for consistent ordering in tests
+ $response = Sku::withSubjectTenantContext()->where('active', true)->orderBy('title')
+ ->get()
+ ->transform(function ($sku) {
+ return $this->skuElement($sku);
+ })
+ ->filter(function ($sku) use ($type) {
+ return !$type || $sku['type'] === $type;
+ })
+ ->sortByDesc('prio')
+ ->values();
+
+ if ($type) {
+ $wallet = $this->guard()->user()->wallet();
+
+ // Figure out the cost for a new object of the specified type
+ $response = $response->map(function ($sku) use ($wallet) {
+ $sku['nextCost'] = $sku['cost'];
+ if ($sku['cost'] && $sku['units_free']) {
+ $count = $wallet->entitlements()->where('sku_id', $sku['id'])->count();
+
+ if ($count < $sku['units_free']) {
+ $sku['nextCost'] = 0;
+ }
+ }
- if (!$this->guard()->user()->canRead($user)) {
- return $this->errorResponse(403);
+ return $sku;
+ });
}
- return $this->objectSkus($user);
+ return response()->json($response->all());
}
/**
@@ -84,9 +57,9 @@
*
* @return \Illuminate\Http\JsonResponse
*/
- protected static function objectSkus($object)
+ public static function objectSkus($object)
{
- $type = $object instanceof \App\Domain ? 'domain' : 'user';
+ $type = \lcfirst(\class_basename($object::class));
$response = [];
// Note: Order by title for consistent ordering in tests
@@ -118,6 +91,75 @@
}
/**
+ * Include SKUs/Wallet information in the object's response.
+ *
+ * @param object $object User/Domain/etc object
+ * @param array $response The response to put the data into
+ */
+ public static function objectEntitlements($object, &$response = []): void
+ {
+ // Object's entitlements information
+ $response['skus'] = \App\Entitlement::objectEntitlementsSummary($object);
+
+ // Some basic information about the object's wallet
+ $wallet = $object->wallet();
+ $response['wallet'] = $wallet->toArray();
+ if ($wallet->discount) {
+ $response['wallet']['discount'] = $wallet->discount->discount;
+ $response['wallet']['discount_description'] = $wallet->discount->description;
+ }
+ }
+
+ /**
+ * Update object entitlements.
+ *
+ * @param object $object The object for update
+ * @param array $rSkus List of SKU IDs requested for the user in the form [id=>qty]
+ */
+ public static function updateEntitlements($object, $rSkus): void
+ {
+ if (!is_array($rSkus)) {
+ return;
+ }
+
+ // list of skus, [id=>obj]
+ $skus = Sku::withEnvTenantContext()->get()->mapWithKeys(
+ function ($sku) {
+ return [$sku->id => $sku];
+ }
+ );
+
+ // existing entitlement's SKUs
+ $eSkus = [];
+
+ $object->entitlements()->groupBy('sku_id')
+ ->selectRaw('count(*) as total, sku_id')->each(
+ function ($e) use (&$eSkus) {
+ $eSkus[$e->sku_id] = $e->total;
+ }
+ );
+
+ foreach ($skus as $skuID => $sku) {
+ $e = array_key_exists($skuID, $eSkus) ? $eSkus[$skuID] : 0;
+ $r = array_key_exists($skuID, $rSkus) ? $rSkus[$skuID] : 0;
+
+ if ($sku->handler_class == \App\Handlers\Mailbox::class) {
+ if ($r != 1) {
+ throw new \Exception("Invalid quantity of mailboxes");
+ }
+ }
+
+ if ($e > $r) {
+ // remove those entitled more than existing
+ $object->removeSku($sku, ($e - $r));
+ } elseif ($e < $r) {
+ // add those requested more than entitled
+ $object->assignSku($sku, ($r - $e));
+ }
+ }
+ }
+
+ /**
* Convert SKU information to metadata used by UI to
* display the form control
*
diff --git a/src/app/Http/Controllers/API/V4/UsersController.php b/src/app/Http/Controllers/API/V4/UsersController.php
--- a/src/app/Http/Controllers/API/V4/UsersController.php
+++ b/src/app/Http/Controllers/API/V4/UsersController.php
@@ -165,6 +165,7 @@
// Check if the user is a controller of his wallet
$isController = $user->canDelete($user);
+ $isDegraded = $user->isDegraded();
$hasCustomDomain = $user->wallet()->entitlements()
->where('entitleable_type', Domain::class)
->count() > 0;
@@ -301,7 +302,7 @@
DB::beginTransaction();
- $this->updateEntitlements($user, $request->skus);
+ SkusController::updateEntitlements($user, $request->skus);
if (!empty($settings)) {
$user->setSettings($settings);
@@ -334,55 +335,6 @@
}
/**
- * Update user entitlements.
- *
- * @param \App\User $user The user
- * @param array $rSkus List of SKU IDs requested for the user in the form [id=>qty]
- */
- protected function updateEntitlements(User $user, $rSkus)
- {
- if (!is_array($rSkus)) {
- return;
- }
-
- // list of skus, [id=>obj]
- $skus = Sku::withEnvTenantContext()->get()->mapWithKeys(
- function ($sku) {
- return [$sku->id => $sku];
- }
- );
-
- // existing entitlement's SKUs
- $eSkus = [];
-
- $user->entitlements()->groupBy('sku_id')
- ->selectRaw('count(*) as total, sku_id')->each(
- function ($e) use (&$eSkus) {
- $eSkus[$e->sku_id] = $e->total;
- }
- );
-
- foreach ($skus as $skuID => $sku) {
- $e = array_key_exists($skuID, $eSkus) ? $eSkus[$skuID] : 0;
- $r = array_key_exists($skuID, $rSkus) ? $rSkus[$skuID] : 0;
-
- if ($sku->handler_class == \App\Handlers\Mailbox::class) {
- if ($r != 1) {
- throw new \Exception("Invalid quantity of mailboxes");
- }
- }
-
- if ($e > $r) {
- // remove those entitled more than existing
- $user->removeSku($sku, ($e - $r));
- } elseif ($e < $r) {
- // add those requested more than entitled
- $user->assignSku($sku, ($r - $e));
- }
- }
- }
-
- /**
* Create a response data array for specified user.
*
* @param \App\User $user User object
diff --git a/src/app/Http/Controllers/RelationController.php b/src/app/Http/Controllers/RelationController.php
--- a/src/app/Http/Controllers/RelationController.php
+++ b/src/app/Http/Controllers/RelationController.php
@@ -321,10 +321,37 @@
$response['aliases'] = $resource->aliases()->pluck('alias')->all();
}
+ // Entitlements/Wallet info
+ if (method_exists($resource, 'wallet')) {
+ API\V4\SkusController::objectEntitlements($resource, $response);
+ }
+
return response()->json($response);
}
/**
+ * Get a list of SKUs available to the resource.
+ *
+ * @param int $id Resource identifier
+ *
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function skus($id)
+ {
+ $resource = $this->model::find($id);
+
+ if (!$this->checkTenant($resource)) {
+ return $this->errorResponse(404);
+ }
+
+ if (!$this->guard()->user()->canRead($resource)) {
+ return $this->errorResponse(403);
+ }
+
+ return API\V4\SkusController::objectSkus($resource);
+ }
+
+ /**
* Fetch resource status (and reload setup process)
*
* @param int $id Resource identifier
diff --git a/src/resources/js/app.js b/src/resources/js/app.js
--- a/src/resources/js/app.js
+++ b/src/resources/js/app.js
@@ -367,18 +367,20 @@
return response
},
error => {
- let loader = error.config.loader
- if (loader) {
- stopLoading(loader)
- }
+ if (error.config) {
+ let loader = error.config.loader
+ if (loader) {
+ stopLoading(loader)
+ }
- // Do not display the error in a toast message, pass the error as-is
- if (axios.isCancel(error) || error.config.ignoreErrors) {
- return Promise.reject(error)
- }
+ // Do not display the error in a toast message, pass the error as-is
+ if (axios.isCancel(error) || error.config.ignoreErrors) {
+ return Promise.reject(error)
+ }
- if (error.config.onFinish) {
- error.config.onFinish()
+ if (error.config.onFinish) {
+ error.config.onFinish()
+ }
}
let error_msg
diff --git a/src/resources/vue/Distlist/Info.vue b/src/resources/vue/Distlist/Info.vue
--- a/src/resources/vue/Distlist/Info.vue
+++ b/src/resources/vue/Distlist/Info.vue
@@ -38,6 +38,10 @@
<list-input id="members" :list="list.members"></list-input>
</div>
</div>
+ <div v-if="list_id === 'new' || list.id" id="distlist-skus" class="row mb-3">
+ <label class="col-sm-4 col-form-label">{{ $t('form.subscriptions') }}</label>
+ <subscription-select class="col-sm-8 pt-sm-1" ref="skus" :object="list" type="group" :readonly="true"></subscription-select>
+ </div>
<btn class="btn-primary" type="submit" icon="check">{{ $t('btn.submit') }}</btn>
</form>
</div>
@@ -65,11 +69,13 @@
<script>
import ListInput from '../Widgets/ListInput'
import StatusComponent from '../Widgets/Status'
+ import SubscriptionSelect from '../Widgets/SubscriptionSelect'
export default {
components: {
ListInput,
- StatusComponent
+ StatusComponent,
+ SubscriptionSelect
},
data() {
return {
@@ -111,13 +117,16 @@
let method = 'post'
let location = '/api/v4/groups'
+ let post = this.$root.pick(this.list, ['name', 'email', 'members'])
if (this.list_id !== 'new') {
method = 'put'
location += '/' + this.list_id
}
- axios[method](location, this.list)
+ // post.skus = this.$refs.skus.getSkus()
+
+ axios[method](location, post)
.then(response => {
this.$toast.success(response.data.message)
this.$router.push({ name: 'distlists' })
@@ -125,7 +134,8 @@
},
submitSettings() {
this.$root.clearFormValidation($('#settings form'))
- let post = this.list.config
+
+ const post = this.$root.pick(this.list.config, [ 'sender_policy' ])
axios.post('/api/v4/groups/' + this.list_id + '/config', post)
.then(response => {
diff --git a/src/resources/vue/Domain/Info.vue b/src/resources/vue/Domain/Info.vue
--- a/src/resources/vue/Domain/Info.vue
+++ b/src/resources/vue/Domain/Info.vue
@@ -31,7 +31,7 @@
</div>
<div v-if="domain.id" id="domain-skus" class="row">
<label class="col-sm-4 col-form-label">{{ $t('user.subscriptions') }}</label>
- <subscription-select v-if="domain.id" class="col-sm-8 pt-sm-1" type="domain" :object="domain" :readonly="true"></subscription-select>
+ <subscription-select v-if="domain.id" class="col-sm-8 pt-sm-1" ref="skus" type="domain" :object="domain" :readonly="true"></subscription-select>
</div>
<btn v-if="!domain.id" class="btn-primary mt-3" type="submit" icon="check">{{ $t('btn.submit') }}</btn>
</form>
diff --git a/src/resources/vue/Resource/Info.vue b/src/resources/vue/Resource/Info.vue
--- a/src/resources/vue/Resource/Info.vue
+++ b/src/resources/vue/Resource/Info.vue
@@ -40,6 +40,10 @@
<input type="text" class="form-control" id="email" disabled v-model="resource.email">
</div>
</div>
+ <div v-if="resource_id === 'new' || resource.id" id="resource-skus" class="row mb-3">
+ <label class="col-sm-4 col-form-label">{{ $t('form.subscriptions') }}</label>
+ <subscription-select class="col-sm-8 pt-sm-1" ref="skus" :object="resource" type="resource" :readonly="true"></subscription-select>
+ </div>
<btn class="btn-primary" type="submit" icon="check">{{ $t('btn.submit') }}</btn>
</form>
</div>
@@ -73,10 +77,12 @@
<script>
import StatusComponent from '../Widgets/Status'
+ import SubscriptionSelect from '../Widgets/SubscriptionSelect'
export default {
components: {
- StatusComponent
+ StatusComponent,
+ SubscriptionSelect
},
data() {
return {
@@ -136,13 +142,14 @@
let method = 'post'
let location = '/api/v4/resources'
+ let post = this.$root.pick(this.resource, ['id', 'name', 'domain'])
if (this.resource_id !== 'new') {
method = 'put'
location += '/' + this.resource_id
}
- const post = this.$root.pick(this.resource, ['id', 'name', 'domain'])
+ // post.skus = this.$refs.skus.getSkus()
axios[method](location, post)
.then(response => {
diff --git a/src/resources/vue/SharedFolder/Info.vue b/src/resources/vue/SharedFolder/Info.vue
--- a/src/resources/vue/SharedFolder/Info.vue
+++ b/src/resources/vue/SharedFolder/Info.vue
@@ -48,6 +48,10 @@
<list-input id="aliases" :list="folder.aliases"></list-input>
</div>
</div>
+ <div v-if="folder_id === 'new' || folder.id" id="folder-skus" class="row mb-3">
+ <label class="col-sm-4 col-form-label">{{ $t('form.subscriptions') }}</label>
+ <subscription-select class="col-sm-8 pt-sm-1" ref="skus" :object="folder" type="shared-folder" :readonly="true"></subscription-select>
+ </div>
<btn class="btn-primary" type="submit" icon="check">{{ $t('btn.submit') }}</btn>
</form>
</div>
@@ -76,12 +80,14 @@
import AclInput from '../Widgets/AclInput'
import ListInput from '../Widgets/ListInput'
import StatusComponent from '../Widgets/Status'
+ import SubscriptionSelect from '../Widgets/SubscriptionSelect'
export default {
components: {
AclInput,
ListInput,
- StatusComponent
+ StatusComponent,
+ SubscriptionSelect
},
data() {
return {
@@ -132,18 +138,19 @@
let method = 'post'
let location = '/api/v4/shared-folders'
+ let post = this.$root.pick(this.folder, ['id', 'name', 'domain', 'type', 'aliases'])
if (this.folder_id !== 'new') {
method = 'put'
location += '/' + this.folder_id
}
- const post = this.$root.pick(this.folder, ['id', 'name', 'domain', 'type', 'aliases'])
-
if (post.type != 'mail') {
delete post.aliases
}
+ // post.skus = this.$refs.skus.getSkus()
+
axios[method](location, post)
.then(response => {
this.$toast.success(response.data.message)
diff --git a/src/resources/vue/Widgets/SubscriptionSelect.vue b/src/resources/vue/Widgets/SubscriptionSelect.vue
--- a/src/resources/vue/Widgets/SubscriptionSelect.vue
+++ b/src/resources/vue/Widgets/SubscriptionSelect.vue
@@ -72,24 +72,36 @@
this.discount_description = this.object.wallet.discount_description
}
- axios.get('/api/v4/' + this.type + 's/' + this.object.id + '/skus', { loader: true })
+ let url = '/api/v4/' + this.type + 's/' + this.object.id + '/skus'
+
+ if (!this.object.id) {
+ url = '/api/v4/skus?type=' + this.type.replace(/-([a-z])/g, (match, p1) => p1.toUpperCase())
+ }
+
+ axios.get(url, { loader: true })
.then(response => {
- if (this.readonly) {
+ if (this.readonly && this.object.skus) {
response.data = response.data.filter(sku => { return sku.id in this.object.skus })
}
// "merge" SKUs with user entitlement-SKUs
this.skus = response.data
.map(sku => {
- const objSku = this.object.skus[sku.id]
+ const objSku = this.object.skus ? this.object.skus[sku.id] : null
if (objSku) {
sku.enabled = true
sku.skuCost = sku.cost
sku.cost = objSku.costs.reduce((sum, current) => sum + current)
sku.value = objSku.count
sku.costs = objSku.costs
- } else if (!sku.readonly) {
- sku.enabled = false
+ } else {
+ if ('nextCost' in sku) {
+ sku.cost = sku.nextCost
+ }
+
+ if (!sku.readonly) {
+ sku.enabled = false
+ }
}
return sku
diff --git a/src/routes/api.php b/src/routes/api.php
--- a/src/routes/api.php
+++ b/src/routes/api.php
@@ -77,7 +77,7 @@
Route::apiResource('domains', API\V4\DomainsController::class);
Route::get('domains/{id}/confirm', [API\V4\DomainsController::class, 'confirm']);
- Route::get('domains/{id}/skus', [API\V4\SkusController::class, 'domainSkus']);
+ Route::get('domains/{id}/skus', [API\V4\DomainsController::class, 'skus']);
Route::get('domains/{id}/status', [API\V4\DomainsController::class, 'status']);
Route::post('domains/{id}/config', [API\V4\DomainsController::class, 'setConfig']);
@@ -95,6 +95,7 @@
}
Route::apiResource('groups', API\V4\GroupsController::class);
+ Route::get('groups/{id}/skus', [API\V4\GroupsController::class, 'skus']);
Route::get('groups/{id}/status', [API\V4\GroupsController::class, 'status']);
Route::post('groups/{id}/config', [API\V4\GroupsController::class, 'setConfig']);
@@ -106,10 +107,12 @@
->withoutMiddleware(['auth:api']);
Route::apiResource('resources', API\V4\ResourcesController::class);
+ Route::get('resources/{id}/skus', [API\V4\ResourcesController::class, 'skus']);
Route::get('resources/{id}/status', [API\V4\ResourcesController::class, 'status']);
Route::post('resources/{id}/config', [API\V4\ResourcesController::class, 'setConfig']);
Route::apiResource('shared-folders', API\V4\SharedFoldersController::class);
+ Route::get('shared-folders/{id}/skus', [API\V4\SharedFoldersController::class, 'skus']);
Route::get('shared-folders/{id}/status', [API\V4\SharedFoldersController::class, 'status']);
Route::post('shared-folders/{id}/config', [API\V4\SharedFoldersController::class, 'setConfig']);
@@ -117,7 +120,7 @@
Route::apiResource('users', API\V4\UsersController::class);
Route::post('users/{id}/config', [API\V4\UsersController::class, 'setConfig']);
- Route::get('users/{id}/skus', [API\V4\SkusController::class, 'userSkus']);
+ Route::get('users/{id}/skus', [API\V4\UsersController::class, 'skus']);
Route::get('users/{id}/status', [API\V4\UsersController::class, 'status']);
Route::apiResource('wallets', API\V4\WalletsController::class);
@@ -181,7 +184,7 @@
],
function () {
Route::apiResource('domains', API\V4\Admin\DomainsController::class);
- Route::get('domains/{id}/skus', [API\V4\Admin\SkusController::class, 'domainSkus']);
+ Route::get('domains/{id}/skus', [API\V4\Admin\DomainsController::class, 'skus']);
Route::post('domains/{id}/suspend', [API\V4\Admin\DomainsController::class, 'suspend']);
Route::post('domains/{id}/unsuspend', [API\V4\Admin\DomainsController::class, 'unsuspend']);
@@ -196,7 +199,7 @@
Route::apiResource('users', API\V4\Admin\UsersController::class);
Route::get('users/{id}/discounts', [API\V4\Reseller\DiscountsController::class, 'userDiscounts']);
Route::post('users/{id}/reset2FA', [API\V4\Admin\UsersController::class, 'reset2FA']);
- Route::get('users/{id}/skus', [API\V4\Admin\SkusController::class, 'userSkus']);
+ Route::get('users/{id}/skus', [API\V4\Admin\UsersController::class, 'skus']);
Route::post('users/{id}/skus/{sku}', [API\V4\Admin\UsersController::class, 'setSku']);
Route::post('users/{id}/suspend', [API\V4\Admin\UsersController::class, 'suspend']);
Route::post('users/{id}/unsuspend', [API\V4\Admin\UsersController::class, 'unsuspend']);
@@ -219,7 +222,7 @@
],
function () {
Route::apiResource('domains', API\V4\Reseller\DomainsController::class);
- Route::get('domains/{id}/skus', [API\V4\Reseller\SkusController::class, 'domainSkus']);
+ Route::get('domains/{id}/skus', [API\V4\Reseller\DomainsController::class, 'skus']);
Route::post('domains/{id}/suspend', [API\V4\Reseller\DomainsController::class, 'suspend']);
Route::post('domains/{id}/unsuspend', [API\V4\Reseller\DomainsController::class, 'unsuspend']);
@@ -246,7 +249,7 @@
Route::apiResource('users', API\V4\Reseller\UsersController::class);
Route::get('users/{id}/discounts', [API\V4\Reseller\DiscountsController::class, 'userDiscounts']);
Route::post('users/{id}/reset2FA', [API\V4\Reseller\UsersController::class, 'reset2FA']);
- Route::get('users/{id}/skus', [API\V4\Reseller\SkusController::class, 'userSkus']);
+ Route::get('users/{id}/skus', [API\V4\Reseller\UsersController::class, 'skus']);
Route::post('users/{id}/skus/{sku}', [API\V4\Admin\UsersController::class, 'setSku']);
Route::post('users/{id}/suspend', [API\V4\Reseller\UsersController::class, 'suspend']);
Route::post('users/{id}/unsuspend', [API\V4\Reseller\UsersController::class, 'unsuspend']);
diff --git a/src/tests/Browser/DistlistTest.php b/src/tests/Browser/DistlistTest.php
--- a/src/tests/Browser/DistlistTest.php
+++ b/src/tests/Browser/DistlistTest.php
@@ -144,6 +144,18 @@
$browser->assertListInputValue([])
->assertValue('@input', '');
})
+ ->assertSeeIn('div.row:nth-child(4) label', 'Subscriptions')
+ ->with('@skus', function ($browser) {
+ $browser->assertElementsCount('tbody tr', 1)
+ ->assertSeeIn('tbody tr:nth-child(1) td.name', 'Group')
+ ->assertSeeIn('tbody tr:nth-child(1) td.price', '0,00 CHF/month')
+ ->assertChecked('tbody tr:nth-child(1) td.selection input')
+ ->assertDisabled('tbody tr:nth-child(1) td.selection input')
+ ->assertTip(
+ 'tbody tr:nth-child(1) td.buttons button',
+ 'Distribution list'
+ );
+ })
->assertSeeIn('button[type=submit]', 'Submit');
})
// Test error conditions
@@ -187,6 +199,18 @@
$browser->assertListInputValue(['test1@gmail.com', 'test2@gmail.com'])
->assertValue('@input', '');
})
+ ->assertSeeIn('div.row:nth-child(5) label', 'Subscriptions')
+ ->with('@skus', function ($browser) {
+ $browser->assertElementsCount('tbody tr', 1)
+ ->assertSeeIn('tbody tr:nth-child(1) td.name', 'Group')
+ ->assertSeeIn('tbody tr:nth-child(1) td.price', '0,00 CHF/month')
+ ->assertChecked('tbody tr:nth-child(1) td.selection input')
+ ->assertDisabled('tbody tr:nth-child(1) td.selection input')
+ ->assertTip(
+ 'tbody tr:nth-child(1) td.buttons button',
+ 'Distribution list'
+ );
+ })
->assertSeeIn('button[type=submit]', 'Submit');
})
// Test error handling
diff --git a/src/tests/Browser/Pages/DistlistInfo.php b/src/tests/Browser/Pages/DistlistInfo.php
--- a/src/tests/Browser/Pages/DistlistInfo.php
+++ b/src/tests/Browser/Pages/DistlistInfo.php
@@ -41,6 +41,7 @@
'@general' => '#general',
'@nav' => 'ul.nav-tabs',
'@settings' => '#settings',
+ '@skus' => '#distlist-skus',
'@status' => '#status-box',
];
}
diff --git a/src/tests/Browser/Pages/ResourceInfo.php b/src/tests/Browser/Pages/ResourceInfo.php
--- a/src/tests/Browser/Pages/ResourceInfo.php
+++ b/src/tests/Browser/Pages/ResourceInfo.php
@@ -41,6 +41,7 @@
'@general' => '#general',
'@nav' => 'ul.nav-tabs',
'@settings' => '#settings',
+ '@skus' => '#resource-skus',
'@status' => '#status-box',
];
}
diff --git a/src/tests/Browser/Pages/SharedFolderInfo.php b/src/tests/Browser/Pages/SharedFolderInfo.php
--- a/src/tests/Browser/Pages/SharedFolderInfo.php
+++ b/src/tests/Browser/Pages/SharedFolderInfo.php
@@ -41,6 +41,7 @@
'@general' => '#general',
'@nav' => 'ul.nav-tabs',
'@settings' => '#settings',
+ '@skus' => '#folder-skus',
'@status' => '#status-box',
];
}
diff --git a/src/tests/Browser/ResourceTest.php b/src/tests/Browser/ResourceTest.php
--- a/src/tests/Browser/ResourceTest.php
+++ b/src/tests/Browser/ResourceTest.php
@@ -21,8 +21,12 @@
{
parent::setUp();
- Resource::whereNotIn('email', ['resource-test1@kolab.org', 'resource-test2@kolab.org'])->delete();
$this->clearBetaEntitlements();
+ Resource::whereNotIn('email', ['resource-test1@kolab.org', 'resource-test2@kolab.org'])->delete();
+ // Remove leftover entitlements that might interfere with the tests
+ \App\Entitlement::where('entitleable_type', 'App\\Resource')
+ ->whereRaw('entitleable_id not in (select id from resources where deleted_at is null)')
+ ->forceDelete();
}
/**
@@ -30,6 +34,7 @@
*/
public function tearDown(): void
{
+ \App\Sku::withEnvTenantContext()->where('title', 'resource')->update(['units_free' => 0]);
Resource::whereNotIn('email', ['resource-test1@kolab.org', 'resource-test2@kolab.org'])->delete();
$this->clearBetaEntitlements();
@@ -118,10 +123,12 @@
->assertErrorPage(403);
});
- // Add beta entitlements
+ // Add beta entitlement
$john = $this->getTestUser('john@kolab.org');
$this->addBetaEntitlement($john);
+ \App\Sku::withEnvTenantContext()->where('title', 'resource')->update(['units_free' => 3]);
+
$this->browse(function (Browser $browser) {
// Create a resource
$browser->visit(new ResourceList())
@@ -140,6 +147,18 @@
->assertSeeIn('div.row:nth-child(2) label', 'Domain')
->assertSelectHasOptions('div.row:nth-child(2) select', ['kolab.org'])
->assertValue('div.row:nth-child(2) select', 'kolab.org')
+ ->assertSeeIn('div.row:nth-child(3) label', 'Subscriptions')
+ ->with('@skus', function ($browser) {
+ $browser->assertElementsCount('tbody tr', 1)
+ ->assertSeeIn('tbody tr:nth-child(1) td.name', 'Resource')
+ ->assertSeeIn('tbody tr:nth-child(1) td.price', '0,00 CHF/month') // one free unit left
+ ->assertChecked('tbody tr:nth-child(1) td.selection input')
+ ->assertDisabled('tbody tr:nth-child(1) td.selection input')
+ ->assertTip(
+ 'tbody tr:nth-child(1) td.buttons button',
+ 'Reservation taker'
+ );
+ })
->assertSeeIn('button[type=submit]', 'Submit');
})
// Test error conditions
@@ -173,6 +192,17 @@
'value',
'/^resource-[0-9]+@kolab\.org$/'
)
+ ->with('@skus', function ($browser) {
+ $browser->assertElementsCount('tbody tr', 1)
+ ->assertSeeIn('tbody tr:nth-child(1) td.name', 'Resource')
+ ->assertSeeIn('tbody tr:nth-child(1) td.price', '0,00 CHF/month')
+ ->assertChecked('tbody tr:nth-child(1) td.selection input')
+ ->assertDisabled('tbody tr:nth-child(1) td.selection input')
+ ->assertTip(
+ 'tbody tr:nth-child(1) td.buttons button',
+ 'Reservation taker'
+ );
+ })
->assertSeeIn('button[type=submit]', 'Submit');
})
// Test error handling
@@ -204,6 +234,21 @@
$this->assertNull(Resource::where('name', 'Test Resource Update')->first());
});
+
+ // Assert Subscription price for the case when there's no free units
+ \App\Sku::withEnvTenantContext()->where('title', 'resource')->update(['units_free' => 2]);
+
+ $this->browse(function (Browser $browser) {
+ $browser->visit('/resource/new')
+ ->on(new ResourceInfo())
+ ->with('@skus', function ($browser) {
+ $browser->assertElementsCount('tbody tr', 1)
+ ->assertSeeIn('tbody tr:nth-child(1) td.name', 'Resource')
+ ->assertSeeIn('tbody tr:nth-child(1) td.price', '1,01 CHF/month')
+ ->assertChecked('tbody tr:nth-child(1) td.selection input')
+ ->assertDisabled('tbody tr:nth-child(1) td.selection input');
+ });
+ });
}
/**
diff --git a/src/tests/Browser/SharedFolderTest.php b/src/tests/Browser/SharedFolderTest.php
--- a/src/tests/Browser/SharedFolderTest.php
+++ b/src/tests/Browser/SharedFolderTest.php
@@ -156,6 +156,18 @@
$browser->assertListInputValue([])
->assertValue('@input', '');
})
+ ->assertSeeIn('div.row:nth-child(5) label', 'Subscriptions')
+ ->with('@skus', function ($browser) {
+ $browser->assertElementsCount('tbody tr', 1)
+ ->assertSeeIn('tbody tr:nth-child(1) td.name', 'Shared Folder')
+ ->assertSeeIn('tbody tr:nth-child(1) td.price', '0,89 CHF/month')
+ ->assertChecked('tbody tr:nth-child(1) td.selection input')
+ ->assertDisabled('tbody tr:nth-child(1) td.selection input')
+ ->assertTip(
+ 'tbody tr:nth-child(1) td.buttons button',
+ 'A shared folder'
+ );
+ })
->assertSeeIn('button[type=submit]', 'Submit');
})
// Test error conditions
@@ -269,6 +281,17 @@
->with(new ListInput('#aliases'), function (Browser $browser) {
$browser->assertListInputValue(['folder-alias1@kolab.org', 'folder-alias2@kolab.org'])
->assertValue('@input', '');
+ })
+ ->with('@skus', function ($browser) {
+ $browser->assertElementsCount('tbody tr', 1)
+ ->assertSeeIn('tbody tr:nth-child(1) td.name', 'Shared Folder')
+ ->assertSeeIn('tbody tr:nth-child(1) td.price', '0,89 CHF/month')
+ ->assertChecked('tbody tr:nth-child(1) td.selection input')
+ ->assertDisabled('tbody tr:nth-child(1) td.selection input')
+ ->assertTip(
+ 'tbody tr:nth-child(1) td.buttons button',
+ 'A shared folder'
+ );
});
})
// change folder name, and remove one alias
diff --git a/src/tests/Feature/Controller/DomainsTest.php b/src/tests/Feature/Controller/DomainsTest.php
--- a/src/tests/Feature/Controller/DomainsTest.php
+++ b/src/tests/Feature/Controller/DomainsTest.php
@@ -5,6 +5,7 @@
use App\Domain;
use App\Entitlement;
use App\Sku;
+use App\Tenant;
use App\User;
use App\Wallet;
use Illuminate\Support\Facades\Queue;
@@ -24,6 +25,7 @@
$this->deleteTestUser('test2@' . \config('app.domain'));
$this->deleteTestUser('test1@domainscontroller.com');
$this->deleteTestDomain('domainscontroller.com');
+ Sku::where('title', 'test')->delete();
}
public function tearDown(): void
@@ -32,6 +34,7 @@
$this->deleteTestUser('test2@' . \config('app.domain'));
$this->deleteTestUser('test1@domainscontroller.com');
$this->deleteTestDomain('domainscontroller.com');
+ Sku::where('title', 'test')->delete();
$domain = $this->getTestDomain('kolab.org');
$domain->settings()->whereIn('key', ['spf_whitelist'])->delete();
@@ -372,6 +375,46 @@
}
/**
+ * Test fetching SKUs list for a domain (GET /domains/<id>/skus)
+ */
+ public function testSkus(): void
+ {
+ $user = $this->getTestUser('john@kolab.org');
+ $domain = $this->getTestDomain('kolab.org');
+
+ // Unauth access not allowed
+ $response = $this->get("api/v4/domains/{$domain->id}/skus");
+ $response->assertStatus(401);
+
+ // Create an sku for another tenant, to make sure it is not included in the result
+ $nsku = Sku::create([
+ 'title' => 'test',
+ 'name' => 'Test',
+ 'description' => '',
+ 'active' => true,
+ 'cost' => 100,
+ 'handler_class' => 'App\Handlers\Domain',
+ ]);
+ $tenant = Tenant::whereNotIn('id', [\config('app.tenant_id')])->first();
+ $nsku->tenant_id = $tenant->id;
+ $nsku->save();
+
+ $response = $this->actingAs($user)->get("api/v4/domains/{$domain->id}/skus");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertCount(1, $json);
+ $this->assertSkuElement('domain-hosting', $json[0], [
+ 'prio' => 0,
+ 'type' => 'domain',
+ 'handler' => 'DomainHosting',
+ 'enabled' => true,
+ 'readonly' => true,
+ ]);
+ }
+
+ /**
* Test fetching domain status (GET /api/v4/domains/<domain-id>/status)
* and forcing setup process update (?refresh=1)
*
diff --git a/src/tests/Feature/Controller/GroupsTest.php b/src/tests/Feature/Controller/GroupsTest.php
--- a/src/tests/Feature/Controller/GroupsTest.php
+++ b/src/tests/Feature/Controller/GroupsTest.php
@@ -250,6 +250,43 @@
$this->assertArrayHasKey('isActive', $json);
$this->assertArrayHasKey('isLdapReady', $json);
$this->assertSame(['sender_policy' => ['test']], $json['config']);
+ $this->assertCount(1, $json['skus']);
+ }
+
+ /**
+ * Test fetching SKUs list for a group (GET /groups/<id>/skus)
+ */
+ public function testSkus(): void
+ {
+ Queue::fake();
+
+ $john = $this->getTestUser('john@kolab.org');
+ $jack = $this->getTestUser('jack@kolab.org');
+
+ $group = $this->getTestGroup('group-test@kolab.org');
+ $group->assignToWallet($john->wallets->first());
+
+ // Unauth access not allowed
+ $response = $this->get("api/v4/groups/{$group->id}/skus");
+ $response->assertStatus(401);
+
+ // Unauthorized access not allowed
+ $response = $this->actingAs($jack)->get("api/v4/groups/{$group->id}/skus");
+ $response->assertStatus(403);
+
+ $response = $this->actingAs($john)->get("api/v4/groups/{$group->id}/skus");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertCount(1, $json);
+ $this->assertSkuElement('group', $json[0], [
+ 'prio' => 0,
+ 'type' => 'group',
+ 'handler' => 'Group',
+ 'enabled' => true,
+ 'readonly' => true,
+ ]);
}
/**
diff --git a/src/tests/Feature/Controller/ResourcesTest.php b/src/tests/Feature/Controller/ResourcesTest.php
--- a/src/tests/Feature/Controller/ResourcesTest.php
+++ b/src/tests/Feature/Controller/ResourcesTest.php
@@ -248,6 +248,43 @@
$this->assertArrayHasKey('isLdapReady', $json);
$this->assertArrayHasKey('isImapReady', $json);
$this->assertSame(['invitation_policy' => 'reject'], $json['config']);
+ $this->assertCount(1, $json['skus']);
+ }
+
+ /**
+ * Test fetching SKUs list for a resource (GET /resources/<id>/skus)
+ */
+ public function testSkus(): void
+ {
+ Queue::fake();
+
+ $john = $this->getTestUser('john@kolab.org');
+ $jack = $this->getTestUser('jack@kolab.org');
+
+ $resource = $this->getTestResource('resource-test@kolab.org');
+ $resource->assignToWallet($john->wallets->first());
+
+ // Unauth access not allowed
+ $response = $this->get("api/v4/resources/{$resource->id}/skus");
+ $response->assertStatus(401);
+
+ // Unauthorized access not allowed
+ $response = $this->actingAs($jack)->get("api/v4/resources/{$resource->id}/skus");
+ $response->assertStatus(403);
+
+ $response = $this->actingAs($john)->get("api/v4/resources/{$resource->id}/skus");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertCount(1, $json);
+ $this->assertSkuElement('resource', $json[0], [
+ 'prio' => 0,
+ 'type' => 'resource',
+ 'handler' => 'Resource',
+ 'enabled' => true,
+ 'readonly' => true,
+ ]);
}
/**
diff --git a/src/tests/Feature/Controller/SharedFoldersTest.php b/src/tests/Feature/Controller/SharedFoldersTest.php
--- a/src/tests/Feature/Controller/SharedFoldersTest.php
+++ b/src/tests/Feature/Controller/SharedFoldersTest.php
@@ -251,6 +251,47 @@
$this->assertArrayHasKey('isLdapReady', $json);
$this->assertArrayHasKey('isImapReady', $json);
$this->assertSame(['acl' => ['anyone, full']], $json['config']);
+ $this->assertCount(1, $json['skus']);
+ }
+
+ /**
+ * Test fetching SKUs list for a shared folder (GET /shared-folders/<id>/skus)
+ */
+ public function testSkus(): void
+ {
+ Queue::fake();
+
+ $john = $this->getTestUser('john@kolab.org');
+ $jack = $this->getTestUser('jack@kolab.org');
+
+ $folder = $this->getTestSharedFolder('folder-test@kolab.org');
+ $folder->assignToWallet($john->wallets->first());
+
+ // Unauth access not allowed
+ $response = $this->get("api/v4/shared-folders/{$folder->id}/skus");
+ $response->assertStatus(401);
+
+ // Unauthorized access not allowed
+ $response = $this->actingAs($jack)->get("api/v4/shared-folders/{$folder->id}/skus");
+ $response->assertStatus(403);
+
+ // Non-existing folder
+ $response = $this->actingAs($john)->get("api/v4/shared-folders/non-existing/skus");
+ $response->assertStatus(404);
+
+ $response = $this->actingAs($john)->get("api/v4/shared-folders/{$folder->id}/skus");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertCount(1, $json);
+ $this->assertSkuElement('shared-folder', $json[0], [
+ 'prio' => 0,
+ 'type' => 'sharedFolder',
+ 'handler' => 'SharedFolder',
+ 'enabled' => true,
+ 'readonly' => true,
+ ]);
}
/**
diff --git a/src/tests/Feature/Controller/SkusTest.php b/src/tests/Feature/Controller/SkusTest.php
--- a/src/tests/Feature/Controller/SkusTest.php
+++ b/src/tests/Feature/Controller/SkusTest.php
@@ -2,7 +2,6 @@
namespace Tests\Feature\Controller;
-use App\Entitlement;
use App\Http\Controllers\API\V4\SkusController;
use App\Sku;
use App\Tenant;
@@ -17,6 +16,7 @@
{
parent::setUp();
+ $this->deleteTestUser('jane@kolabnow.com');
$this->clearBetaEntitlements();
$this->clearMeetEntitlements();
Sku::where('title', 'test')->delete();
@@ -27,6 +27,7 @@
*/
public function tearDown(): void
{
+ $this->deleteTestUser('jane@kolabnow.com');
$this->clearBetaEntitlements();
$this->clearMeetEntitlements();
Sku::where('title', 'test')->delete();
@@ -35,47 +36,6 @@
}
/**
- * Test fetching SKUs list for a domain (GET /domains/<id>/skus)
- */
- public function testDomainSkus(): void
- {
- $user = $this->getTestUser('john@kolab.org');
- $domain = $this->getTestDomain('kolab.org');
-
- // Unauth access not allowed
- $response = $this->get("api/v4/domains/{$domain->id}/skus");
- $response->assertStatus(401);
-
- // Create an sku for another tenant, to make sure it is not included in the result
- $nsku = Sku::create([
- 'title' => 'test',
- 'name' => 'Test',
- 'description' => '',
- 'active' => true,
- 'cost' => 100,
- 'handler_class' => 'App\Handlers\Domain',
- ]);
- $tenant = Tenant::whereNotIn('id', [\config('app.tenant_id')])->first();
- $nsku->tenant_id = $tenant->id;
- $nsku->save();
-
- $response = $this->actingAs($user)->get("api/v4/domains/{$domain->id}/skus");
- $response->assertStatus(200);
-
- $json = $response->json();
-
- $this->assertCount(1, $json);
-
- $this->assertSkuElement('domain-hosting', $json[0], [
- 'prio' => 0,
- 'type' => 'domain',
- 'handler' => 'DomainHosting',
- 'enabled' => false,
- 'readonly' => false,
- ]);
- }
-
- /**
* Test fetching SKUs list
*/
public function testIndex(): void
@@ -84,7 +44,8 @@
$response = $this->get("api/v4/skus");
$response->assertStatus(401);
- $user = $this->getTestUser('john@kolab.org');
+ $john = $this->getTestUser('john@kolab.org');
+ $jack = $this->getTestUser('jack@kolab.org');
$sku = Sku::withEnvTenantContext()->where('title', 'mailbox')->first();
// Create an sku for another tenant, to make sure it is not included in the result
@@ -100,7 +61,7 @@
$nsku->tenant_id = $tenant->id;
$nsku->save();
- $response = $this->actingAs($user)->get("api/v4/skus");
+ $response = $this->actingAs($john)->get("api/v4/skus");
$response->assertStatus(200);
$json = $response->json();
@@ -118,138 +79,29 @@
$this->assertSame($sku->active, $json[0]['active']);
$this->assertSame('user', $json[0]['type']);
$this->assertSame('Mailbox', $json[0]['handler']);
- }
-
- /**
- * Test fetching SKUs list for a user (GET /users/<id>/skus)
- */
- public function testUserSkus(): void
- {
- $user = $this->getTestUser('john@kolab.org');
-
- // Unauth access not allowed
- $response = $this->get("api/v4/users/{$user->id}/skus");
- $response->assertStatus(401);
- // Create an sku for another tenant, to make sure it is not included in the result
- $nsku = Sku::create([
- 'title' => 'test',
- 'name' => 'Test',
- 'description' => '',
- 'active' => true,
- 'cost' => 100,
- 'handler_class' => 'Mailbox',
- ]);
- $tenant = Tenant::whereNotIn('id', [\config('app.tenant_id')])->first();
- $nsku->tenant_id = $tenant->id;
- $nsku->save();
-
- $response = $this->actingAs($user)->get("api/v4/users/{$user->id}/skus");
+ // Test the type filter, and nextCost property (user with one domain)
+ $response = $this->actingAs($john)->get("api/v4/skus?type=domain");
$response->assertStatus(200);
$json = $response->json();
- $this->assertCount(6, $json);
-
- $this->assertSkuElement('mailbox', $json[0], [
- 'prio' => 100,
- 'type' => 'user',
- 'handler' => 'Mailbox',
- 'enabled' => true,
- 'readonly' => true,
- ]);
-
- $this->assertSkuElement('storage', $json[1], [
- 'prio' => 90,
- 'type' => 'user',
- 'handler' => 'Storage',
- 'enabled' => true,
- 'readonly' => true,
- 'range' => [
- 'min' => 5,
- 'max' => 100,
- 'unit' => 'GB',
- ]
- ]);
-
- $this->assertSkuElement('groupware', $json[2], [
- 'prio' => 80,
- 'type' => 'user',
- 'handler' => 'Groupware',
- 'enabled' => false,
- 'readonly' => false,
- ]);
-
- $this->assertSkuElement('activesync', $json[3], [
- 'prio' => 70,
- 'type' => 'user',
- 'handler' => 'Activesync',
- 'enabled' => false,
- 'readonly' => false,
- 'required' => ['Groupware'],
- ]);
-
- $this->assertSkuElement('2fa', $json[4], [
- 'prio' => 60,
- 'type' => 'user',
- 'handler' => 'Auth2F',
- 'enabled' => false,
- 'readonly' => false,
- 'forbidden' => ['Activesync'],
- ]);
+ $this->assertCount(1, $json);
+ $this->assertSame('domain-hosting', $json[0]['title']);
+ $this->assertSame(100, $json[0]['nextCost']); // second domain costs 100
- $this->assertSkuElement('meet', $json[5], [
- 'prio' => 50,
- 'type' => 'user',
- 'handler' => 'Meet',
- 'enabled' => false,
- 'readonly' => false,
- 'required' => ['Groupware'],
- ]);
+ // Test the type filter, and nextCost property (user with no domain)
+ $jane = $this->getTestUser('jane@kolabnow.com');
+ $kolab = \App\Package::withEnvTenantContext()->where('title', 'kolab')->first();
+ $jane->assignPackage($kolab);
- // Test inclusion of beta SKUs
- $sku = Sku::withEnvTenantContext()->where('title', 'beta')->first();
- $user->assignSku($sku);
- $response = $this->actingAs($user)->get("api/v4/users/{$user->id}/skus");
+ $response = $this->actingAs($jane)->get("api/v4/skus?type=domain");
$response->assertStatus(200);
$json = $response->json();
- $this->assertCount(6, $json);
-
- $this->assertSkuElement('beta', $json[6], [
- 'prio' => 10,
- 'type' => 'user',
- 'handler' => 'Beta',
- 'enabled' => false,
- 'readonly' => false,
- ]);
- }
-
- /**
- * Assert content of the SKU element in an API response
- *
- * @param string $sku_title The SKU title
- * @param array $result The result to assert
- * @param array $other Other items the SKU itself does not include
- */
- protected function assertSkuElement($sku_title, $result, $other = []): void
- {
- $sku = Sku::withEnvTenantContext()->where('title', $sku_title)->first();
-
- $this->assertSame($sku->id, $result['id']);
- $this->assertSame($sku->title, $result['title']);
- $this->assertSame($sku->name, $result['name']);
- $this->assertSame($sku->description, $result['description']);
- $this->assertSame($sku->cost, $result['cost']);
- $this->assertSame($sku->units_free, $result['units_free']);
- $this->assertSame($sku->period, $result['period']);
- $this->assertSame($sku->active, $result['active']);
-
- foreach ($other as $key => $value) {
- $this->assertSame($value, $result[$key]);
- }
-
- $this->assertCount(8 + count($other), $result);
+ $this->assertCount(1, $json);
+ $this->assertSame('domain-hosting', $json[0]['title']);
+ $this->assertSame(0, $json[0]['nextCost']); // first domain costs 0
}
}
diff --git a/src/tests/Feature/Controller/UsersTest.php b/src/tests/Feature/Controller/UsersTest.php
--- a/src/tests/Feature/Controller/UsersTest.php
+++ b/src/tests/Feature/Controller/UsersTest.php
@@ -7,6 +7,7 @@
use App\Http\Controllers\API\V4\UsersController;
use App\Package;
use App\Sku;
+use App\Tenant;
use App\User;
use App\Wallet;
use Carbon\Carbon;
@@ -36,6 +37,7 @@
$this->deleteTestGroup('group-test@kolab.org');
$this->deleteTestSharedFolder('folder-test@kolabnow.com');
$this->deleteTestResource('resource-test@kolabnow.com');
+ Sku::where('title', 'test')->delete();
$user = $this->getTestUser('john@kolab.org');
$wallet = $user->wallets()->first();
@@ -64,6 +66,7 @@
$this->deleteTestGroup('group-test@kolab.org');
$this->deleteTestSharedFolder('folder-test@kolabnow.com');
$this->deleteTestResource('resource-test@kolabnow.com');
+ Sku::where('title', 'test')->delete();
$user = $this->getTestUser('john@kolab.org');
$wallet = $user->wallets()->first();
@@ -350,6 +353,103 @@
}
/**
+ * Test fetching SKUs list for a user (GET /users/<id>/skus)
+ */
+ public function testSkus(): void
+ {
+ $user = $this->getTestUser('john@kolab.org');
+
+ // Unauth access not allowed
+ $response = $this->get("api/v4/users/{$user->id}/skus");
+ $response->assertStatus(401);
+
+ // Create an sku for another tenant, to make sure it is not included in the result
+ $nsku = Sku::create([
+ 'title' => 'test',
+ 'name' => 'Test',
+ 'description' => '',
+ 'active' => true,
+ 'cost' => 100,
+ 'handler_class' => 'Mailbox',
+ ]);
+ $tenant = Tenant::whereNotIn('id', [\config('app.tenant_id')])->first();
+ $nsku->tenant_id = $tenant->id;
+ $nsku->save();
+
+ $response = $this->actingAs($user)->get("api/v4/users/{$user->id}/skus");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertCount(5, $json);
+
+ $this->assertSkuElement('mailbox', $json[0], [
+ 'prio' => 100,
+ 'type' => 'user',
+ 'handler' => 'Mailbox',
+ 'enabled' => true,
+ 'readonly' => true,
+ ]);
+
+ $this->assertSkuElement('storage', $json[1], [
+ 'prio' => 90,
+ 'type' => 'user',
+ 'handler' => 'Storage',
+ 'enabled' => true,
+ 'readonly' => true,
+ 'range' => [
+ 'min' => 5,
+ 'max' => 100,
+ 'unit' => 'GB',
+ ]
+ ]);
+
+ $this->assertSkuElement('groupware', $json[2], [
+ 'prio' => 80,
+ 'type' => 'user',
+ 'handler' => 'Groupware',
+ 'enabled' => false,
+ 'readonly' => false,
+ ]);
+
+ $this->assertSkuElement('activesync', $json[3], [
+ 'prio' => 70,
+ 'type' => 'user',
+ 'handler' => 'Activesync',
+ 'enabled' => false,
+ 'readonly' => false,
+ 'required' => ['Groupware'],
+ ]);
+
+ $this->assertSkuElement('2fa', $json[4], [
+ 'prio' => 60,
+ 'type' => 'user',
+ 'handler' => 'Auth2F',
+ 'enabled' => false,
+ 'readonly' => false,
+ 'forbidden' => ['Activesync'],
+ ]);
+
+ // Test inclusion of beta SKUs
+ $sku = Sku::withEnvTenantContext()->where('title', 'beta')->first();
+ $user->assignSku($sku);
+ $response = $this->actingAs($user)->get("api/v4/users/{$user->id}/skus");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertCount(6, $json);
+
+ $this->assertSkuElement('beta', $json[5], [
+ 'prio' => 10,
+ 'type' => 'user',
+ 'handler' => 'Beta',
+ 'enabled' => false,
+ 'readonly' => false,
+ ]);
+ }
+
+ /**
* Test fetching user status (GET /api/v4/users/<user-id>/status)
* and forcing setup process update (?refresh=1)
*
diff --git a/src/tests/TestCaseTrait.php b/src/tests/TestCaseTrait.php
--- a/src/tests/TestCaseTrait.php
+++ b/src/tests/TestCaseTrait.php
@@ -131,6 +131,33 @@
Assert::assertSame($expected, $skus);
}
+ /**
+ * Assert content of the SKU element in an API response
+ *
+ * @param string $sku_title The SKU title
+ * @param array $result The result to assert
+ * @param array $other Other items the SKU itself does not include
+ */
+ protected function assertSkuElement($sku_title, $result, $other = []): void
+ {
+ $sku = Sku::withEnvTenantContext()->where('title', $sku_title)->first();
+
+ $this->assertSame($sku->id, $result['id']);
+ $this->assertSame($sku->title, $result['title']);
+ $this->assertSame($sku->name, $result['name']);
+ $this->assertSame($sku->description, $result['description']);
+ $this->assertSame($sku->cost, $result['cost']);
+ $this->assertSame($sku->units_free, $result['units_free']);
+ $this->assertSame($sku->period, $result['period']);
+ $this->assertSame($sku->active, $result['active']);
+
+ foreach ($other as $key => $value) {
+ $this->assertSame($value, $result[$key]);
+ }
+
+ $this->assertCount(8 + count($other), $result);
+ }
+
protected function backdateEntitlements($entitlements, $targetDate, $targetCreatedDate = null)
{
$wallets = [];
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Thu, Apr 2, 9:09 PM (4 d, 4 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18820873
Default Alt Text
D3614.1775164150.diff (62 KB)
Attached To
Mode
D3614: Display Subscriptions section for all entitleable objects
Attached
Detach File
Event Timeline