Commit d6dbe34f authored by Douglas Barbosa Alexandre's avatar Douglas Barbosa Alexandre

Merge branch 'ag/341564-subs-name' into 'master'

Retrieve and use (current) subscription name for finalising the purchase

See merge request gitlab-org/gitlab!71932
parents c4c7aa03 34a4563e
......@@ -16,15 +16,15 @@ export default {
},
},
mounted() {
this.updateSelectedPlanId(this.plan.id);
this.updateSelectedPlan(this.plan);
},
methods: {
updateSelectedPlanId(planId) {
updateSelectedPlan({ id, isAddon }) {
this.$apollo
.mutate({
mutation: updateState,
variables: {
input: { selectedPlanId: planId },
input: { selectedPlan: { id, isAddon } },
},
})
.catch((error) => {
......
......@@ -8,6 +8,11 @@ export const planTags = {
/* eslint-enable @gitlab/require-i18n-strings */
export const CUSTOMERSDOT_CLIENT = 'customersDotClient';
export const GITLAB_CLIENT = 'gitlabClient';
export const CUSTOMER_TYPE = 'Customer';
export const SUBSCRIPTION_TYPE = 'Subscription';
export const NAMESPACE_TYPE = 'Namespace';
export const PAYMENT_METHOD_TYPE = 'PaymentMethod';
export const PLAN_TYPE = 'Plan';
export const CI_MINUTES_PER_PACK = 1000;
export const STORAGE_PER_PACK = 10;
......
import {
CUSTOMER_TYPE,
NAMESPACE_TYPE,
SUBSCRIPTION_TYPE,
PAYMENT_METHOD_TYPE,
PLAN_TYPE,
} from 'ee/subscriptions/buy_addons_shared/constants';
import { STEPS } from 'ee/subscriptions/constants';
import stateQuery from 'ee/subscriptions/graphql/queries/state.query.graphql';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
......@@ -11,26 +18,38 @@ function arrayToGraphqlArray(arr, typename) {
}
export function writeInitialDataToApolloCache(apolloProvider, dataset) {
const { groupData, namespaceId, redirectAfterSuccess, subscriptionQuantity } = dataset;
const {
activeSubscriptionName = '',
groupData,
namespaceId,
redirectAfterSuccess,
subscriptionQuantity,
} = dataset;
// eslint-disable-next-line @gitlab/require-i18n-strings
const eligibleNamespaces = arrayToGraphqlArray(JSON.parse(groupData), 'Namespace');
const eligibleNamespaces = arrayToGraphqlArray(JSON.parse(groupData), NAMESPACE_TYPE);
const quantity = subscriptionQuantity || 1;
apolloProvider.clients.defaultClient.cache.writeQuery({
query: stateQuery,
data: {
activeSubscription: {
name: activeSubscriptionName,
__typename: SUBSCRIPTION_TYPE,
},
isNewUser: false,
fullName: null,
isSetupForCompany: false,
selectedPlanId: null,
selectedPlan: {
id: null,
isAddon: false,
__typename: PLAN_TYPE,
},
eligibleNamespaces,
redirectAfterSuccess,
selectedNamespaceId: namespaceId,
subscription: {
quantity,
// eslint-disable-next-line @gitlab/require-i18n-strings
__typename: 'Subscription',
__typename: SUBSCRIPTION_TYPE,
},
paymentMethod: {
id: null,
......@@ -38,7 +57,7 @@ export function writeInitialDataToApolloCache(apolloProvider, dataset) {
creditCardExpirationYear: null,
creditCardType: null,
creditCardMaskNumber: null,
__typename: 'PaymentMethod',
__typename: PAYMENT_METHOD_TYPE,
},
customer: {
country: null,
......@@ -48,8 +67,7 @@ export function writeInitialDataToApolloCache(apolloProvider, dataset) {
state: null,
zipCode: null,
company: null,
// eslint-disable-next-line @gitlab/require-i18n-strings
__typename: 'Customer',
__typename: CUSTOMER_TYPE,
},
activeStep: STEPS[0],
stepList: STEPS,
......
......@@ -55,6 +55,14 @@ export default {
hasError: false,
};
},
computed: {
plan() {
return {
...this.plans[0],
isAddon: true,
};
},
},
methods: {
isQuantityValid(quantity) {
return Number.isFinite(quantity) && quantity > 0;
......@@ -102,7 +110,6 @@ export default {
this.hasError = true;
return null;
}
return data.plans;
},
error(error) {
......@@ -122,7 +129,7 @@ export default {
/>
<step-order-app v-else-if="!$apollo.loading">
<template #checkout>
<checkout :plan="plans[0]">
<checkout :plan="plan">
<template #purchase-details>
<addon-purchase-details
:product-label="$options.i18n.productLabel"
......@@ -145,7 +152,7 @@ export default {
</checkout>
</template>
<template #order-summary>
<order-summary :plan="plans[0]" :title="$options.i18n.title">
<order-summary :plan="plan" :title="$options.i18n.title">
<template #price-per-unit="{ price }">
{{ pricePerUnitLabel(price) }}
</template>
......
......@@ -5,10 +5,16 @@ query State {
name
users
}
activeSubscription @client {
name
}
isNewUser @client
fullName @client
isSetupForCompany @client
selectedPlanId @client
selectedPlan @client {
id
isAddon
}
selectedNamespaceId @client
redirectAfterSuccess @client
customer @client {
......
......@@ -35,11 +35,13 @@ export default {
},
update(data) {
const { customer } = data;
const { name } = data.activeSubscription;
return {
setup_for_company: data.isSetupForCompany,
selected_group: data.selectedNamespaceId,
new_user: data.isNewUser,
redirect_after_success: data.redirectAfterSuccess,
active_subscription: name,
customer: {
country: customer.country,
address_1: customer.address1,
......@@ -50,9 +52,10 @@ export default {
company: customer.company,
},
subscription: {
plan_id: data.selectedPlanId,
payment_method_id: data.paymentMethod.id,
quantity: data.subscription.quantity,
is_addon: data.selectedPlan.isAddon,
plan_id: data.selectedPlan.id,
payment_method_id: data.paymentMethod.id,
},
};
},
......
......@@ -36,11 +36,12 @@ class SubscriptionsController < ApplicationController
return render_404 unless ci_minutes_plan_data.present?
# At the moment of this comment the account id is directly available to the view.
# This might change in the future given the intention to associate the account id to the namespace.
# This might change in the future given the intention to associate the account id to the namespace.
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/338546#note_684762160
result = find_group(plan_id: ci_minutes_plan_data["id"])
@group = result[:namespace]
@account_id = result[:account_id]
@active_subscription = result[:active_subscription]
return render_404 if @group.nil?
......@@ -51,11 +52,12 @@ class SubscriptionsController < ApplicationController
return render_404 unless storage_plan_data.present?
# At the moment of this comment the account id is directly available to the view.
# This might change in the future given the intention to associate the account id to the namespace.
# This might change in the future given the intention to associate the account id to the namespace.
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/338546#note_684762160
result = find_group(plan_id: storage_plan_data["id"])
@group = result[:namespace]
@account_id = result[:account_id]
@active_subscription = result[:active_subscription]
return render_404 if @group.nil?
......@@ -110,7 +112,7 @@ class SubscriptionsController < ApplicationController
end
def subscription_params
params.require(:subscription).permit(:plan_id, :payment_method_id, :quantity, :source)
params.require(:subscription).permit(:plan_id, :is_addon, :payment_method_id, :quantity, :source).merge(params.permit(:active_subscription))
end
def find_group(plan_id:)
......
......@@ -16,8 +16,9 @@ module SubscriptionsHelper
}
end
def buy_addon_data(group, account_id, anchor, purchased_product)
def buy_addon_data(group, account_id, active_subscription, anchor, purchased_product)
{
active_subscription: active_subscription,
group_data: [present_group(group, account_id)].to_json,
namespace_id: params[:selected_group],
redirect_after_success: group_usage_quotas_path(group, anchor: anchor, purchased_product: purchased_product),
......
......@@ -25,10 +25,14 @@ module GitlabSubscriptions
return missing_plan_error if plan_id.nil? && any_self_service_plan.nil?
if response[:success] && response[:data]
eligible_namespaces = response[:data].to_h { |data| [data["id"], data["accountId"]] }
eligible_namespaces = response[:data].to_h { |data| [data["id"], [data["accountId"], data['subscription']]] }
data = namespaces.each_with_object([]) do |namespace, acc|
if eligible_namespaces.include?(namespace.id)
acc << { namespace: namespace, account_id: eligible_namespaces[namespace.id] }
acc << {
namespace: namespace,
account_id: eligible_namespaces[namespace.id][0],
active_subscription: eligible_namespaces[namespace.id][1]
}
end
end
......
......@@ -25,10 +25,7 @@ module Subscriptions
# We can't use an email from GL.com because it may differ from the billing email.
# Instead we use the email received from the CustomersDot as a billing email.
customer_data = response.with_indifferent_access[:data][:customer]
billing_email = customer_data[:email]
token = customer_data[:authentication_token]
response = client.create_subscription(create_subscription_params, billing_email, token)
response = create_subscription(customer_data)
OnboardingProgressService.new(@group).execute(action: :subscription_created) if response[:success]
......@@ -73,15 +70,20 @@ module Subscriptions
}
end
def create_subscription_params
def create_subscription(customer_data)
# When purchasing an add on, we don't want to send create_subscription_params
# in order to avoid amending the main product. Note that this will go away
# when fully transitioning the flow to GraphQL
create_params = add_on? ? create_addon_params : create_subscription_params
billing_email, token = customer_data.values_at(:email, :authentication_token)
client.create_subscription(create_params, billing_email, token)
end
def create_params
{
plan_id: subscription_params[:plan_id],
payment_method_id: subscription_params[:payment_method_id],
products: {
main: {
quantity: subscription_params[:quantity]
}
},
gl_namespace_id: @group.id,
gl_namespace_name: @group.name,
preview: 'false',
......@@ -89,6 +91,27 @@ module Subscriptions
}
end
def create_addon_params
{
active_subscription: subscription_params[:active_subscription],
quantity: subscription_params[:quantity]
}.merge(create_params)
end
def create_subscription_params
{
products: {
main: {
quantity: subscription_params[:quantity]
}
}
}.merge(create_params)
end
def add_on?
Gitlab::Utils.to_boolean(subscription_params[:is_addon], default: false)
end
def country_code(country)
World.alpha3_from_alpha2(country)
end
......
......@@ -2,4 +2,4 @@
- content_for :page_specific_javascripts do
= render "layouts/one_trust"
#js-buy-minutes{ data: buy_addon_data(@group, @account_id, 'pipelines-quota-tab', s_('Checkout|CI minutes')) }
#js-buy-minutes{ data: buy_addon_data(@group, @account_id, @active_subscription, 'pipelines-quota-tab', s_('Checkout|CI minutes')) }
......@@ -2,4 +2,4 @@
- content_for :page_specific_javascripts do
= render "layouts/one_trust"
#js-buy-storage{ data: buy_addon_data(@group, @account_id, 'storage-quota-tab', s_('Checkout|a storage subscription')) }
#js-buy-storage{ data: buy_addon_data(@group, @account_id, @active_subscription, 'storage-quota-tab', s_('Checkout|a storage subscription')) }
......@@ -124,6 +124,7 @@ module Gitlab
namespaceEligibility(customerUid: $customerUid, namespaces: $namespaces, planId: $planId, eligibleForPurchase: $eligibleForPurchase) {
id
accountId: zuoraAccountId
subscription { name }
}
}
GQL
......
{
"customer": {
"provider": "gitlab",
"uid": 111,
"credentials": {
"token": "foo_token"
},
[
{
"customer": {
"country": "NLD",
"address_1": "Address line 1",
"address_2": "Address line 2",
"city": "City",
"state": "State",
"zip_code": "Zip code",
"company": "My organization"
"provider": "gitlab",
"uid": 111,
"credentials": {
"token": "foo_token"
},
"customer": {
"country": "NLD",
"address_1": "Address line 1",
"address_2": "Address line 2",
"city": "City",
"state": "State",
"zip_code": "Zip code",
"company": "My organization"
},
"info": {
"first_name": "First name",
"last_name": "Last name",
"email": "first.last@gitlab.com"
}
},
"info": {
"first_name": "First name",
"last_name": "Last name",
"email": "first.last@gitlab.com"
"subscription": {
"plan_id": "Plan ID",
"payment_method_id": "Payment method ID",
"products": {
"main": {
"quantity": 123
}
},
"gl_namespace_id": 222,
"gl_namespace_name": "Group name",
"preview": "false",
"source": "some_source"
}
},
"subscription": {
"plan_id": "Plan ID",
"payment_method_id": "Payment method ID",
"products": {
"main": {
"quantity": 123
{
"customer": {
"provider": "gitlab",
"uid": 111,
"credentials": {
"token": "foo_token"
},
"customer": {
"country": "NLD",
"address_1": "Address line 1",
"address_2": "Address line 2",
"city": "City",
"state": "State",
"zip_code": "Zip code",
"company": "My organization"
},
"info": {
"first_name": "First name",
"last_name": "Last name",
"email": "first.last@gitlab.com"
}
},
"gl_namespace_id": 222,
"gl_namespace_name": "Group name",
"preview": "false",
"source": "some_source"
"subscription": {
"plan_id": "Add-on Plan ID",
"payment_method_id": "Payment method ID",
"quantity": 111,
"active_subscription": "A-000000",
"gl_namespace_id": 222,
"gl_namespace_name": "Group name",
"preview": "false",
"source": "some_source"
}
}
}
]
......@@ -59,7 +59,6 @@ describe('Order Summary', () => {
beforeEach(() => {
createComponent({
subscription: { quantity: 1 },
selectedPlanId: 'ciMinutesPackPlanId',
});
});
......
import { GlEmptyState } from '@gitlab/ui';
import { createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import { pick } from 'lodash';
import { I18N_CI_MINUTES_TITLE, planTags } from 'ee/subscriptions/buy_addons_shared/constants';
import Checkout from 'ee/subscriptions/buy_addons_shared/components/checkout.vue';
import AddonPurchaseDetails from 'ee/subscriptions/buy_addons_shared/components/checkout/addon_purchase_details.vue';
import OrderSummary from 'ee/subscriptions/buy_addons_shared/components/order_summary.vue';
......@@ -9,8 +11,8 @@ import App from 'ee/subscriptions/buy_minutes/components/app.vue';
import StepOrderApp from 'ee/vue_shared/purchase_flow/components/step_order_app.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { planTags } from '../../../../../app/assets/javascripts/subscriptions/buy_addons_shared/constants';
import { createMockApolloProvider } from '../spec_helper';
import { mockCiMinutesPlans } from '../mock_data';
const localVue = createLocalVue();
localVue.use(VueApollo);
......@@ -32,24 +34,42 @@ describe('App', () => {
});
}
const getCiMinutePlan = () => pick(mockCiMinutesPlans[0], ['id', 'code', 'pricePerYear', 'name']);
const findCheckout = () => wrapper.findComponent(Checkout);
const findOrderSummary = () => wrapper.findComponent(OrderSummary);
const findPriceLabel = () => wrapper.findByTestId('price-per-unit');
const findQuantityText = () => wrapper.findByTestId('addon-quantity-text');
const findSummaryLabel = () => wrapper.findByTestId('summary-label');
const findSummaryTotal = () => wrapper.findByTestId('summary-total');
const findPriceLabel = () => wrapper.findByTestId('price-per-unit');
afterEach(() => {
wrapper.destroy();
});
describe('when data is received', () => {
it('should display the StepOrderApp', async () => {
beforeEach(() => {
const mockApollo = createMockApolloProvider();
wrapper = createComponent(mockApollo);
await waitForPromises();
return waitForPromises();
});
it('should display the StepOrderApp', () => {
expect(wrapper.findComponent(StepOrderApp).exists()).toBe(true);
expect(wrapper.findComponent(GlEmptyState).exists()).toBe(false);
});
it('provides the correct props to checkout', () => {
expect(findCheckout().props()).toMatchObject({
plan: { ...getCiMinutePlan, isAddon: true },
});
});
it('provides the correct props to order summary', () => {
expect(findOrderSummary().props()).toMatchObject({
plan: { ...getCiMinutePlan, isAddon: true },
title: I18N_CI_MINUTES_TITLE,
});
});
});
describe('when data is not received', () => {
......
......@@ -6,7 +6,10 @@ import { STEPS } from 'ee/subscriptions/constants';
import stateQuery from 'ee/subscriptions/graphql/queries/state.query.graphql';
import ConfirmOrder from 'ee/vue_shared/purchase_flow/components/checkout/confirm_order.vue';
import { GENERAL_ERROR_MESSAGE } from 'ee/vue_shared/purchase_flow/constants';
import { stateData as initialStateData } from 'ee_jest/subscriptions/buy_minutes/mock_data';
import {
stateData as initialStateData,
subscriptionName,
} from 'ee_jest/subscriptions/buy_minutes/mock_data';
import { createMockApolloProvider } from 'ee_jest/vue_shared/purchase_flow/spec_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import flash from '~/flash';
......@@ -40,17 +43,13 @@ describe('Confirm Order', () => {
wrapper.destroy();
});
describe('Active', () => {
describe('when rendering', () => {
describe('when receiving proper step data', () => {
beforeEach(() => {
mockApolloProvider = createMockApolloProvider(STEPS, 3);
createComponent({ apolloProvider: mockApolloProvider });
});
it('button should be visible', () => {
expect(findConfirmButton().exists()).toBe(true);
});
it('shows the text "Confirm purchase"', () => {
expect(findConfirmButton().text()).toBe('Confirm purchase');
});
......@@ -60,7 +59,7 @@ describe('Confirm Order', () => {
});
});
describe('Clicking the button', () => {
describe('when confirming the order', () => {
beforeEach(() => {
mockApolloProvider = createMockApolloProvider([]);
mockApolloProvider.clients.defaultClient.cache.writeQuery({
......@@ -74,9 +73,10 @@ describe('Confirm Order', () => {
it('calls the confirmOrder API method with the correct params', () => {
expect(Api.confirmOrder).toHaveBeenCalledTimes(1);
expect(Api.confirmOrder.mock.calls[0][0]).toMatchObject({
expect(Api.confirmOrder.mock.calls[0][0]).toEqual({
setup_for_company: true,
selected_group: '30',
active_subscription: subscriptionName,
new_user: false,
redirect_after_success: '/path/to/redirect/',
customer: {
......@@ -90,6 +90,7 @@ describe('Confirm Order', () => {
},
subscription: {
plan_id: null,
is_addon: true,
payment_method_id: null,
quantity: 1,
},
......@@ -105,35 +106,33 @@ describe('Confirm Order', () => {
});
});
describe('Order confirmation', () => {
describe('when the confirmation succeeds', () => {
const location = 'group/location/path';
describe('when confirming the purchase', () => {
const location = 'group/location/path';
beforeEach(() => {
mockApolloProvider = createMockApolloProvider(STEPS, 3);
createComponent({ apolloProvider: mockApolloProvider });
});
beforeEach(() => {
mockApolloProvider = createMockApolloProvider(STEPS, 3);
createComponent({ apolloProvider: mockApolloProvider });
});
it('should redirect to the location', async () => {
Api.confirmOrder = jest.fn().mockResolvedValueOnce({ data: { location } });
findConfirmButton().vm.$emit('click');
await flushPromises();
it('redirects to the location if it succeeds', async () => {
Api.confirmOrder = jest.fn().mockResolvedValueOnce({ data: { location } });
findConfirmButton().vm.$emit('click');
await flushPromises();
expect(UrlUtility.redirectTo).toHaveBeenCalledTimes(1);
expect(UrlUtility.redirectTo).toHaveBeenCalledWith(location);
});
expect(UrlUtility.redirectTo).toHaveBeenCalledTimes(1);
expect(UrlUtility.redirectTo).toHaveBeenCalledWith(location);
});
it('shows an error if it fails', async () => {
const errors = 'an error';
Api.confirmOrder = jest.fn().mockResolvedValueOnce({ data: { errors } });
findConfirmButton().vm.$emit('click');
await flushPromises();
it('shows an error', async () => {
const errors = 'an error';
Api.confirmOrder = jest.fn().mockResolvedValueOnce({ data: { errors } });
findConfirmButton().vm.$emit('click');
await flushPromises();
expect(flash.mock.calls[0][0]).toMatchObject({
message: GENERAL_ERROR_MESSAGE,
captureError: true,
error: new Error(JSON.stringify(errors)),
});
expect(flash.mock.calls[0][0]).toMatchObject({
message: GENERAL_ERROR_MESSAGE,
captureError: true,
error: new Error(JSON.stringify(errors)),
});
});
});
......@@ -163,7 +162,7 @@ describe('Confirm Order', () => {
});
});
describe('Inactive', () => {
describe('when inactive', () => {
it('does not show buttons', () => {
mockApolloProvider = createMockApolloProvider(STEPS, 1);
createComponent({ apolloProvider: mockApolloProvider });
......
import { STEPS } from 'ee/subscriptions/constants';
import {
CUSTOMER_TYPE,
NAMESPACE_TYPE,
PAYMENT_METHOD_TYPE,
PLAN_TYPE,
SUBSCRIPTION_TYPE,
} from 'ee/subscriptions/buy_addons_shared/constants';
export const accountId = '111111111111';
export const subscriptionName = 'A-000000000';
export const mockCiMinutesPlans = [
{
......@@ -8,7 +16,7 @@ export const mockCiMinutesPlans = [
code: 'ci_minutes',
pricePerYear: 10,
name: 'CI minutes pack',
__typename: 'Plan',
__typename: PLAN_TYPE,
},
];
......@@ -19,7 +27,7 @@ export const mockNamespaces = `
export const mockParsedNamespaces = JSON.parse(mockNamespaces).map((namespace) => ({
...namespace,
__typename: 'Namespace',
__typename: NAMESPACE_TYPE,
}));
export const mockNewUser = 'false';
......@@ -35,18 +43,25 @@ export const stateData = {
eligibleNamespaces: [],
subscription: {
quantity: 1,
__typename: 'Subscription',
__typename: SUBSCRIPTION_TYPE,
},
activeSubscription: {
name: subscriptionName,
__typename: SUBSCRIPTION_TYPE,
},
redirectAfterSuccess: '/path/to/redirect/',
selectedNamespaceId: '30',
selectedPlanId: null,
selectedPlan: {
id: null,
isAddon: true,
},
paymentMethod: {
id: null,
creditCardExpirationMonth: null,
creditCardExpirationYear: null,
creditCardType: null,
creditCardMaskNumber: null,
__typename: 'PaymentMethod',
__typename: PAYMENT_METHOD_TYPE,
},
customer: {
country: null,
......@@ -56,7 +71,7 @@ export const stateData = {
state: null,
zipCode: null,
company: null,
__typename: 'Customer',
__typename: CUSTOMER_TYPE,
},
fullName: 'Full Name',
isNewUser: false,
......
......@@ -134,11 +134,12 @@ RSpec.describe SubscriptionsHelper do
end
describe '#buy_addon_data' do
subject(:buy_addon_data) { helper.buy_addon_data(group, account_id, anchor, purchased_product) }
subject(:buy_addon_data) { helper.buy_addon_data(group, account_id, active_subscription, anchor, purchased_product) }
let_it_be(:group) { create(:group, name: 'My Namespace') }
let_it_be(:user) { create(:user, name: 'First Last') }
let_it_be(:account_id) { '111111111111' }
let_it_be(:active_subscription) { { name: 'S-000000000' } }
let(:anchor) { 'pipelines-quota-tab' }
let(:purchased_product) { 'CI Minutes' }
......@@ -150,6 +151,7 @@ RSpec.describe SubscriptionsHelper do
end
it { is_expected.to include(namespace_id: group.id.to_s) }
it { is_expected.to include(active_subscription: active_subscription) }
it { is_expected.to include(source: 'some_source') }
it { is_expected.to include(group_data: %Q{[{"id":#{group.id},"account_id":"#{account_id}","name":"My Namespace","users":1,"guests":0}]}) }
it { is_expected.to include(redirect_after_success: group_usage_quotas_path(group, anchor: anchor, purchased_product: purchased_product)) }
......
......@@ -323,6 +323,7 @@ RSpec.describe Gitlab::SubscriptionPortal::Clients::Graphql do
namespaceEligibility(customerUid: $customerUid, namespaces: $namespaces, planId: $planId, eligibleForPurchase: $eligibleForPurchase) {
id
accountId: zuoraAccountId
subscription { name }
}
}
GQL
......
......@@ -67,13 +67,15 @@ RSpec.describe GitlabSubscriptions::FetchPurchaseEligibleNamespacesService do
end
context 'when all the namespaces are eligible' do
let(:subscription_name) { 'S-000000' }
before do
allow(Gitlab::SubscriptionPortal::Client)
.to receive(:filter_purchase_eligible_namespaces)
.with(user, [namespace_1, namespace_2], plan_id: 'test', any_self_service_plan: nil)
.and_return(success: true, data: [
{ 'id' => namespace_1.id, 'accountId' => nil },
{ 'id' => namespace_2.id, 'accountId' => nil }
{ 'id' => namespace_1.id, 'accountId' => nil, 'subscription' => { 'name' => subscription_name } },
{ 'id' => namespace_2.id, 'accountId' => nil, 'subscription' => nil }
])
end
......@@ -83,8 +85,8 @@ RSpec.describe GitlabSubscriptions::FetchPurchaseEligibleNamespacesService do
expect(result).to be_success
expect(result.payload).to match_array [
namespace_result(namespace_1, nil),
namespace_result(namespace_2, nil)
namespace_result(namespace_1, nil, { 'name' => subscription_name }),
namespace_result(namespace_2, nil, nil)
]
end
end
......@@ -105,7 +107,7 @@ RSpec.describe GitlabSubscriptions::FetchPurchaseEligibleNamespacesService do
expect(result).to be_success
expect(result.payload).to match_array [
namespace_result(namespace_1, account_id)
namespace_result(namespace_1, account_id, nil)
]
end
end
......@@ -124,7 +126,7 @@ RSpec.describe GitlabSubscriptions::FetchPurchaseEligibleNamespacesService do
expect(result).to be_success
expect(result.payload).to match_array [
namespace_result(namespace_1, nil)
namespace_result(namespace_1, nil, nil)
]
end
end
......@@ -132,7 +134,7 @@ RSpec.describe GitlabSubscriptions::FetchPurchaseEligibleNamespacesService do
private
def namespace_result(namespace, account_id)
{ namespace: namespace, account_id: account_id }
def namespace_result(namespace, account_id, active_subscription)
{ namespace: namespace, account_id: account_id, active_subscription: active_subscription }
end
end
......@@ -32,7 +32,7 @@ RSpec.describe Subscriptions::CreateService do
let_it_be(:customer_email) { 'first.last@gitlab.com' }
let_it_be(:client) { Gitlab::SubscriptionPortal::Client }
let_it_be(:create_service_params) { Gitlab::Json.parse(fixture_file('create_service_params.json', dir: 'ee')).deep_symbolize_keys }
let_it_be(:create_service_params) { Gitlab::Json.parse(fixture_file('create_service_params.json', dir: 'ee'))[0].deep_symbolize_keys }
describe '#execute' do
before do
......@@ -123,6 +123,27 @@ RSpec.describe Subscriptions::CreateService do
execute
end
context 'with add-on purchase' do
let_it_be(:subscription_params) do
{
is_addon: true,
active_subscription: 'A-000000',
plan_id: 'Add-on Plan ID',
payment_method_id: 'Payment method ID',
quantity: 111,
source: 'some_source'
}
end
it 'passes the correct parameters for creating a subscription' do
create_service_addon_params = Gitlab::Json.parse(fixture_file('create_service_params.json', dir: 'ee'))[1].deep_symbolize_keys
expect(client).to receive(:create_subscription).with(create_service_addon_params[:subscription], customer_email, 'token')
execute
end
end
it_behaves_like 'records an onboarding progress action', :subscription_created do
let(:namespace) { group }
end
......
......@@ -21,8 +21,13 @@ RSpec.shared_examples_for 'subscription form data' do |js_selector|
end
RSpec.shared_examples_for 'buy minutes addon form data' do |js_selector|
let_it_be(:group) { create(:group) }
let_it_be(:account_id) { '111111111111' }
let_it_be(:active_subscription) { { name: 'S-000000000' } }
before do
allow(view).to receive(:buy_addon_data).and_return(
allow(view).to receive(:buy_addon_data).with(@group, @account_id, @active_subscription, 'pipelines-quota-tab', s_('Checkout|CI minutes')).and_return(
active_subscription: active_subscription,
group_data: '[{"id":"ci_minutes_plan_id","code":"ci_minutes","price_per_year":10.0}]',
namespace_id: '1',
plan_id: 'ci_minutes_plan_id',
......@@ -33,6 +38,7 @@ RSpec.shared_examples_for 'buy minutes addon form data' do |js_selector|
subject { render }
it { is_expected.to have_selector("#{js_selector}[data-active-subscription-name='S-000000000']") }
it { is_expected.to have_selector("#{js_selector}[data-group-data='[{\"id\":\"ci_minutes_plan_id\",\"code\":\"ci_minutes\",\"price_per_year\":10.0}]']") }
it { is_expected.to have_selector("#{js_selector}[data-plan-id='ci_minutes_plan_id']") }
it { is_expected.to have_selector("#{js_selector}[data-namespace-id='1']") }
......@@ -41,8 +47,13 @@ RSpec.shared_examples_for 'buy minutes addon form data' do |js_selector|
end
RSpec.shared_examples_for 'buy storage addon form data' do |js_selector|
let_it_be(:group) { create(:group) }
let_it_be(:account_id) { '111111111111' }
let_it_be(:active_subscription) { { name: 'S-000000000' } }
before do
allow(view).to receive(:buy_addon_data).and_return(
allow(view).to receive(:buy_addon_data).with(@group, @account_id, @active_subscription, 'storage-quota-tab', s_('Checkout|a storage subscription')).and_return(
active_subscription: active_subscription,
group_data: '[{"id":"storage_plan_id","code":"storage","price_per_year":10.0}]',
namespace_id: '2',
plan_id: 'storage_plan_id',
......@@ -53,6 +64,7 @@ RSpec.shared_examples_for 'buy storage addon form data' do |js_selector|
subject { render }
it { is_expected.to have_selector("#{js_selector}[data-active-subscription-name='S-000000000']") }
it { is_expected.to have_selector("#{js_selector}[data-group-data='[{\"id\":\"storage_plan_id\",\"code\":\"storage\",\"price_per_year\":10.0}]']") }
it { is_expected.to have_selector("#{js_selector}[data-plan-id='storage_plan_id']") }
it { is_expected.to have_selector("#{js_selector}[data-namespace-id='2']") }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment