Skip to content

Commit

Permalink
fix(core): Limit Channel defaultLanguage to one of availableLanguages
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelbromley committed Apr 6, 2020
1 parent aae4aa9 commit b9f4dc0
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 16 deletions.
87 changes: 73 additions & 14 deletions packages/core/e2e/channel.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {
Me,
Permission,
RemoveProductsFromChannel,
UpdateChannel,
UpdateGlobalSettings,
} from './graphql/generated-e2e-admin-types';
import {
ASSIGN_PRODUCT_TO_CHANNEL,
Expand Down Expand Up @@ -100,8 +102,8 @@ describe('Channels', () => {

expect(me!.channels.length).toBe(2);

const secondChannelData = me!.channels.find(c => c.token === SECOND_CHANNEL_TOKEN);
const nonOwnerPermissions = Object.values(Permission).filter(p => p !== Permission.Owner);
const secondChannelData = me!.channels.find((c) => c.token === SECOND_CHANNEL_TOKEN);
const nonOwnerPermissions = Object.values(Permission).filter((p) => p !== Permission.Owner);
expect(secondChannelData!.permissions).toEqual(nonOwnerPermissions);
});

Expand All @@ -111,7 +113,7 @@ describe('Channels', () => {

expect(me!.channels.length).toBe(2);

const secondChannelData = me!.channels.find(c => c.token === SECOND_CHANNEL_TOKEN);
const secondChannelData = me!.channels.find((c) => c.token === SECOND_CHANNEL_TOKEN);
expect(me!.channels).toEqual([
{
code: DEFAULT_CHANNEL_CODE,
Expand Down Expand Up @@ -170,7 +172,7 @@ describe('Channels', () => {
},
});

expect(createAdministrator.user.roles.map(r => r.description)).toEqual(['second channel admin']);
expect(createAdministrator.user.roles.map((r) => r.description)).toEqual(['second channel admin']);
});

it(
Expand Down Expand Up @@ -288,7 +290,7 @@ describe('Channels', () => {
},
});

expect(assignProductsToChannel[0].channels.map(c => c.id).sort()).toEqual(['T_1', 'T_2']);
expect(assignProductsToChannel[0].channels.map((c) => c.id).sort()).toEqual(['T_1', 'T_2']);
await adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
const { product } = await adminClient.query<
GetProductWithVariants.Query,
Expand All @@ -297,12 +299,12 @@ describe('Channels', () => {
id: product1.id,
});

expect(product!.variants.map(v => v.price)).toEqual(
product1.variants.map(v => v.price * PRICE_FACTOR),
expect(product!.variants.map((v) => v.price)).toEqual(
product1.variants.map((v) => v.price * PRICE_FACTOR),
);
// Second Channel is configured to include taxes in price, so they should be the same.
expect(product!.variants.map(v => v.priceWithTax)).toEqual(
product1.variants.map(v => v.price * PRICE_FACTOR),
expect(product!.variants.map((v) => v.priceWithTax)).toEqual(
product1.variants.map((v) => v.price * PRICE_FACTOR),
);
});

Expand All @@ -317,7 +319,7 @@ describe('Channels', () => {
},
});

expect(assignProductsToChannel[0].channels.map(c => c.id).sort()).toEqual(['T_1', 'T_2']);
expect(assignProductsToChannel[0].channels.map((c) => c.id).sort()).toEqual(['T_1', 'T_2']);
});

it(
Expand Down Expand Up @@ -348,7 +350,44 @@ describe('Channels', () => {
},
});

expect(removeProductsFromChannel[0].channels.map(c => c.id)).toEqual(['T_1']);
expect(removeProductsFromChannel[0].channels.map((c) => c.id)).toEqual(['T_1']);
});
});

describe('setting defaultLanguage', () => {
it(
'throws if languageCode not in availableLanguages',
assertThrowsWithMessage(async () => {
await adminClient.query<UpdateChannel.Mutation, UpdateChannel.Variables>(UPDATE_CHANNEL, {
input: {
id: 'T_1',
defaultLanguageCode: LanguageCode.zh,
},
});
}, 'Language "zh" is not available. First enable it via GlobalSettings and try again.'),
);

it('allows setting to an available language', async () => {
await adminClient.query<UpdateGlobalSettings.Mutation, UpdateGlobalSettings.Variables>(
UPDATE_GLOBAL_SETTINGS,
{
input: {
availableLanguages: [LanguageCode.en, LanguageCode.zh],
},
},
);

const { updateChannel } = await adminClient.query<
UpdateChannel.Mutation,
UpdateChannel.Variables
>(UPDATE_CHANNEL, {
input: {
id: 'T_1',
defaultLanguageCode: LanguageCode.zh,
},
});

expect(updateChannel.defaultLanguageCode).toBe(LanguageCode.zh);
});
});

Expand All @@ -364,7 +403,7 @@ describe('Channels', () => {
productIds: [PROD_ID],
},
});
expect(assignProductsToChannel[0].channels.map(c => c.id).sort()).toEqual(['T_1', 'T_2']);
expect(assignProductsToChannel[0].channels.map((c) => c.id).sort()).toEqual(['T_1', 'T_2']);

const { deleteChannel } = await adminClient.query<DeleteChannel.Mutation, DeleteChannel.Variables>(
DELETE_CHANNEL,
Expand All @@ -376,15 +415,15 @@ describe('Channels', () => {
expect(deleteChannel.result).toBe(DeletionResult.DELETED);

const { channels } = await adminClient.query<GetChannels.Query>(GET_CHANNELS);
expect(channels.map(c => c.id).sort()).toEqual(['T_1', 'T_3']);
expect(channels.map((c) => c.id).sort()).toEqual(['T_1', 'T_3']);

const { product } = await adminClient.query<
GetProductWithVariants.Query,
GetProductWithVariants.Variables
>(GET_PRODUCT_WITH_VARIANTS, {
id: PROD_ID,
});
expect(product!.channels.map(c => c.id)).toEqual(['T_1']);
expect(product!.channels.map((c) => c.id)).toEqual(['T_1']);
});
});

Expand All @@ -398,6 +437,17 @@ const GET_CHANNELS = gql`
}
`;

const UPDATE_CHANNEL = gql`
mutation UpdateChannel($input: UpdateChannelInput!) {
updateChannel(input: $input) {
id
code
defaultLanguageCode
currencyCode
}
}
`;

const DELETE_CHANNEL = gql`
mutation DeleteChannel($id: ID!) {
deleteChannel(id: $id) {
Expand All @@ -406,3 +456,12 @@ const DELETE_CHANNEL = gql`
}
}
`;

const UPDATE_GLOBAL_SETTINGS = gql`
mutation UpdateGlobalSettings($input: UpdateGlobalSettingsInput!) {
updateGlobalSettings(input: $input) {
id
availableLanguages
}
}
`;
34 changes: 34 additions & 0 deletions packages/core/e2e/graphql/generated-e2e-admin-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3585,6 +3585,17 @@ export type GetChannelsQuery = { __typename?: 'Query' } & {
channels: Array<{ __typename?: 'Channel' } & Pick<Channel, 'id' | 'code' | 'token'>>;
};

export type UpdateChannelMutationVariables = {
input: UpdateChannelInput;
};

export type UpdateChannelMutation = { __typename?: 'Mutation' } & {
updateChannel: { __typename?: 'Channel' } & Pick<
Channel,
'id' | 'code' | 'defaultLanguageCode' | 'currencyCode'
>;
};

export type DeleteChannelMutationVariables = {
id: Scalars['ID'];
};
Expand All @@ -3593,6 +3604,17 @@ export type DeleteChannelMutation = { __typename?: 'Mutation' } & {
deleteChannel: { __typename?: 'DeletionResponse' } & Pick<DeletionResponse, 'message' | 'result'>;
};

export type UpdateGlobalSettingsMutationVariables = {
input: UpdateGlobalSettingsInput;
};

export type UpdateGlobalSettingsMutation = { __typename?: 'Mutation' } & {
updateGlobalSettings: { __typename?: 'GlobalSettings' } & Pick<
GlobalSettings,
'id' | 'availableLanguages'
>;
};

export type GetCollectionsWithAssetsQueryVariables = {};

export type GetCollectionsWithAssetsQuery = { __typename?: 'Query' } & {
Expand Down Expand Up @@ -5454,12 +5476,24 @@ export namespace GetChannels {
export type Channels = NonNullable<GetChannelsQuery['channels'][0]>;
}

export namespace UpdateChannel {
export type Variables = UpdateChannelMutationVariables;
export type Mutation = UpdateChannelMutation;
export type UpdateChannel = UpdateChannelMutation['updateChannel'];
}

export namespace DeleteChannel {
export type Variables = DeleteChannelMutationVariables;
export type Mutation = DeleteChannelMutation;
export type DeleteChannel = DeleteChannelMutation['deleteChannel'];
}

export namespace UpdateGlobalSettings {
export type Variables = UpdateGlobalSettingsMutationVariables;
export type Mutation = UpdateGlobalSettingsMutation;
export type UpdateGlobalSettings = UpdateGlobalSettingsMutation['updateGlobalSettings'];
}

export namespace GetCollectionsWithAssets {
export type Variables = GetCollectionsWithAssetsQueryVariables;
export type Query = GetCollectionsWithAssetsQuery;
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/i18n/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"identifier-change-token-not-recognized": "Identifier change token not recognized",
"identifier-change-token-has-expired": "Identifier change token has expired",
"invalid-sort-field": "The sort field '{ fieldName }' is invalid. Valid fields are: { validFields }",
"language-not-available-in-global-settings": "Language \"{code}\" is not available. First enable it via GlobalSettings and try again.",
"missing-password-on-registration": "A password must be provided when `authOptions.requireVerification` is set to \"false\"",
"no-search-plugin-configured": "No search plugin has been configured",
"no-valid-channel-specified": "No valid channel was specified (ensure the 'vendure-token' header was specified in the request)",
Expand Down
25 changes: 23 additions & 2 deletions packages/core/src/service/services/channel.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ import { unique } from '@vendure/common/lib/unique';
import { Connection } from 'typeorm';

import { RequestContext } from '../../api/common/request-context';
import { ChannelNotFoundError, EntityNotFoundError, InternalServerError } from '../../common/error/errors';
import {
ChannelNotFoundError,
EntityNotFoundError,
InternalServerError,
UserInputError,
} from '../../common/error/errors';
import { ChannelAware } from '../../common/types/common-types';
import { assertFound, idsAreEqual } from '../../common/utils';
import { ConfigService } from '../../config/config.service';
Expand All @@ -24,11 +29,17 @@ import { Zone } from '../../entity/zone/zone.entity';
import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
import { patchEntity } from '../helpers/utils/patch-entity';

import { GlobalSettingsService } from './global-settings.service';

@Injectable()
export class ChannelService {
private allChannels: Channel[] = [];

constructor(@InjectConnection() private connection: Connection, private configService: ConfigService) {}
constructor(
@InjectConnection() private connection: Connection,
private configService: ConfigService,
private globalSettingsService: GlobalSettingsService,
) {}

/**
* When the app is bootstrapped, ensure a default Channel exists and populate the
Expand Down Expand Up @@ -147,6 +158,16 @@ export class ChannelService {
if (!channel) {
throw new EntityNotFoundError('Channel', input.id);
}
if (input.defaultLanguageCode) {
const availableLanguageCodes = await this.globalSettingsService
.getSettings()
.then((s) => s.availableLanguages);
if (!availableLanguageCodes.includes(input.defaultLanguageCode)) {
throw new UserInputError('error.language-not-available-in-global-settings', {
code: input.defaultLanguageCode,
});
}
}
const updatedChannel = patchEntity(channel, input);
if (input.defaultTaxZoneId) {
updatedChannel.defaultTaxZone = await getEntityOrThrow(
Expand Down

0 comments on commit b9f4dc0

Please sign in to comment.