Page MenuHomePhorge

D3614.1775164150.diff
No OneTemporary

Authored By
Unknown
Size
62 KB
Referenced Files
None
Subscribers
None

D3614.1775164150.diff

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

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)

Event Timeline