BUYClientTest.m 18.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
//
//  BUYClientTest.m
//  Mobile Buy SDK
//
//  Created by Shopify.
//  Copyright (c) 2015 Shopify Inc. All rights reserved.
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
//

@import UIKit;
@import XCTest;

#import <Buy/Buy.h>
#import "BUYTestConstants.h"
#import "BUYClientTestBase.h"
33
#import "NSURLComponents+BUYAdditions.h"
34
#import "BUYShopifyErrorCodes.h"
35 36
#import "BUYAccountCredentials.h"
#import "BUYClient+Customers.h"
37
#import "BUYClient+Internal.h"
38
#import "BUYApplePayToken.h"
39
#import "BUYApplePayTestToken.h"
40
#import "BUYRequestOperation.h"
41 42

NSString * const BUYFakeCustomerToken = @"dsfasdgafdg";
43 44 45 46 47 48 49

@interface BUYClient_Test : BUYClient

@end

@implementation BUYClient_Test

50
- (void)startOperation:(BUYOperation *)operation
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
{
	// Do nothing
}

@end

@interface BUYClientTest : BUYClientTestBase
@end

@implementation BUYClientTest

- (void)setupClient
{
	self.shopDomain = BUYShopDomain_Placeholder;
	self.apiKey = BUYAPIKey_Placeholder;
66
	self.appId = BUYAppId_Placeholder;
67
	
68
	self.client = [[BUYClient_Test alloc] initWithShopDomain:self.shopDomain apiKey:self.apiKey appId:self.appId];
69 70 71 72
}

- (NSData *)dataForCartFromClient:(BUYClient *)client
{
73
	BUYCart *cart = [self cart];
74
	BUYCheckout *checkout = [[BUYCheckout alloc] initWithCart:cart];
75
	BUYRequestOperation *task = [self.client createCheckout:checkout completion:^(BUYCheckout *checkout, NSError *error) {}];
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
	XCTAssertNotNil(task);
	
	NSURLRequest *request = task.originalRequest;
	XCTAssertNotNil(request);
	
	NSData *data = request.HTTPBody;
	XCTAssertNotNil(data);
	
	return data;
}

- (void)testCheckoutSerialization
{
	NSData *data = [self dataForCartFromClient:self.client];
	
	NSDictionary *dict = @{@"checkout":
							   @{@"line_items": @[],
								 @"source_name": @"mobile_app",
								 @"marketing_attribution":@{@"medium": @"iOS", @"source": self.client.applicationName}}};
	
	NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
	XCTAssertEqualObjects(dict, json);
}

- (void)testPartialAddressesFlag
{
102
	BUYCart *cart = [self cart];
103
	BUYCheckout *checkout = [[BUYCheckout alloc] initWithCart:cart];
104
	
105
	XCTAssertThrows([checkout setPartialAddressesValue:NO]);
106

107
	BUYRequestOperation *task = [self.client createCheckout:checkout completion:^(BUYCheckout *checkout, NSError *error) {}];
108 109 110 111 112
	NSDictionary *json = [NSJSONSerialization JSONObjectWithData:task.originalRequest.HTTPBody options:0 error:nil];
	XCTAssertFalse([json[@"checkout"][@"partial_addresses"] boolValue]);
	
	checkout = [[BUYCheckout alloc] initWithCart:cart];
	
113
	BUYAddress *partialAddress = [self.client.modelManager insertAddressWithJSONDictionary:nil];
114 115 116
	partialAddress.address1 = nil;
	
	if ([partialAddress isPartialAddress]) {
117
		checkout.partialAddressesValue = YES;
118
	}
119 120
	
	checkout.shippingAddress = partialAddress;
Dima Bart committed
121
	task = [self.client createCheckout:checkout completion:^(BUYCheckout *checkout, NSError *error) {}];
122 123 124 125 126 127 128
	json = [NSJSONSerialization JSONObjectWithData:task.originalRequest.HTTPBody options:0 error:nil];

	XCTAssertTrue([json[@"checkout"][@"partial_addresses"] boolValue]);
}

- (void)testCheckoutPaymentWithOnlyGiftCard
{
129
	BUYOperation *task = [self.client completeCheckoutForToken:@"abcdef" paymentToken:nil completion:^(BUYCheckout *checkout, NSError *error) {}];
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
	XCTAssertNotNil(task);
}

- (void)testCheckoutURLParsing
{
	NSURL *url = [NSURL URLWithString:@"sampleapp://?checkout%5Btoken%5D=377a6afb2c6651b6c42af5547e12bda1"];
	
	[self.client getCompletionStatusOfCheckoutURL:url completion:^(BUYStatus status, NSError *error) {
		// We should not get a callback here
		XCTFail();
	}];
}

- (void)testCheckoutBadURLParsing
{
	NSURL *url = [NSURL URLWithString:@"sampleapp://"];
	
Dima Bart committed
147
	XCTAssertThrows(
Dima Bart committed
148
		[self.client getCompletionStatusOfCheckoutURL:url completion:^(BUYStatus status, NSError *error) {}]
Dima Bart committed
149
	);
150 151 152 153
}

- (void)testStatusCodeConversions
{
154
	BUYStatus status = [self.client statusForStatusCode:412 error:nil];
155 156
	XCTAssertEqual(BUYStatusPreconditionFailed, status);
	
157
	status = [self.client statusForStatusCode:404 error:nil];
158 159
	XCTAssertEqual(BUYStatusNotFound, status);
	
160
	status = [self.client statusForStatusCode:0 error:[NSError errorWithDomain:@"" code:-1 userInfo:nil]];
161 162
	XCTAssertEqual(BUYStatusFailed, status);
	
163
	status = [self.client statusForStatusCode:424 error:nil];
164 165
	XCTAssertEqual(BUYStatusFailed, status);
	
166
	status = [self.client statusForStatusCode:202 error:nil];
167 168
	XCTAssertEqual(BUYStatusProcessing, status);
	
169
	status = [self.client statusForStatusCode:200 error:nil];
170 171 172
	XCTAssertEqual(BUYStatusComplete, status);
}

173 174 175 176 177 178 179 180 181 182
- (void)testQueryItemsConversion
{
	NSDictionary *dictionary = @{@"collection_id" : @"1", @"limit" : @"25", @"page" : @"1", @"sort_by" : @"collection-default"};
	NSURLComponents *components = [[NSURLComponents alloc] init];
	[components setQueryItemsWithDictionary:dictionary];
	NSSet *componentsQueryItems = [NSSet setWithArray:components.queryItems];
	NSSet *queryItems = [NSSet setWithArray:@[[NSURLQueryItem queryItemWithName:@"collection_id" value:@"1"], [NSURLQueryItem queryItemWithName:@"limit" value:@"25"], [NSURLQueryItem queryItemWithName:@"page" value:@"1"], [NSURLQueryItem queryItemWithName:@"sort_by" value:@"collection-default"]]];
	XCTAssertEqualObjects(componentsQueryItems, queryItems);
}

183 184
- (void)testProductsInCollectionWithSortOrderCollectionDefault
{
185
	BUYRequestOperation *task = [self.client getProductsPage:1 inCollection:@1 sortOrder:BUYCollectionSortCollectionDefault completion:^(NSArray<BUYProduct *> *products, NSUInteger page, BOOL reachedEnd, NSError *error) {}];
Rune Madsen committed
186
	XCTAssertEqualObjects(task.originalRequest.HTTPMethod, @"GET");
187 188
	XCTAssertEqualObjects(task.originalRequest.URL.scheme, @"https");
	XCTAssertEqualObjects(task.originalRequest.URL.host, @"test_shop");
189
	XCTAssertEqualObjects(task.originalRequest.URL.path, @"/api/apps/app_id/product_listings.json");
190 191 192
	NSSet *requestQueryItems = [NSSet setWithArray:[task.originalRequest.URL.query componentsSeparatedByString:@"&"]];
	NSSet *queryItems = [NSSet setWithArray:@[@"collection_id=1", @"limit=25", @"page=1", @"sort_by=collection-default"]];
	XCTAssertEqualObjects(requestQueryItems, queryItems);
193 194 195 196
}

- (void)testProductsInCollectionWithSortOrderBestSelling
{
197
	BUYRequestOperation *task = [self.client getProductsPage:1 inCollection:@1 sortOrder:BUYCollectionSortBestSelling completion:^(NSArray<BUYProduct *> *products, NSUInteger page, BOOL reachedEnd, NSError *error) {}];
Rune Madsen committed
198
	XCTAssertEqualObjects(task.originalRequest.HTTPMethod, @"GET");
199 200
	XCTAssertEqualObjects(task.originalRequest.URL.scheme, @"https");
	XCTAssertEqualObjects(task.originalRequest.URL.host, @"test_shop");
201
	XCTAssertEqualObjects(task.originalRequest.URL.path, @"/api/apps/app_id/product_listings.json");
202 203 204
	NSSet *requestQueryItems = [NSSet setWithArray:[task.originalRequest.URL.query componentsSeparatedByString:@"&"]];
	NSSet *queryItems = [NSSet setWithArray:@[@"collection_id=1", @"limit=25", @"page=1", @"sort_by=best-selling"]];
	XCTAssertEqualObjects(requestQueryItems, queryItems);
205 206 207 208
}

- (void)testProductsInCollectionWithSortOrderCreatedAscending
{
209
	BUYRequestOperation *task = [self.client getProductsPage:1 inCollection:@1 sortOrder:BUYCollectionSortCreatedAscending completion:^(NSArray<BUYProduct *> *products, NSUInteger page, BOOL reachedEnd, NSError *error) {}];
Rune Madsen committed
210
	XCTAssertEqualObjects(task.originalRequest.HTTPMethod, @"GET");
211 212
	XCTAssertEqualObjects(task.originalRequest.URL.scheme, @"https");
	XCTAssertEqualObjects(task.originalRequest.URL.host, @"test_shop");
213
	XCTAssertEqualObjects(task.originalRequest.URL.path, @"/api/apps/app_id/product_listings.json");
214 215 216
	NSSet *requestQueryItems = [NSSet setWithArray:[task.originalRequest.URL.query componentsSeparatedByString:@"&"]];
	NSSet *queryItems = [NSSet setWithArray:@[@"collection_id=1", @"limit=25", @"page=1", @"sort_by=created-ascending"]];
	XCTAssertEqualObjects(requestQueryItems, queryItems);
217 218 219 220
}

- (void)testProductsInCollectionWithSortOrderCreatedDescending
{
221
	BUYRequestOperation *task = [self.client getProductsPage:1 inCollection:@1 sortOrder:BUYCollectionSortCreatedDescending completion:^(NSArray<BUYProduct *> *products, NSUInteger page, BOOL reachedEnd, NSError *error) {}];
Rune Madsen committed
222
	XCTAssertEqualObjects(task.originalRequest.HTTPMethod, @"GET");
223 224
	XCTAssertEqualObjects(task.originalRequest.URL.scheme, @"https");
	XCTAssertEqualObjects(task.originalRequest.URL.host, @"test_shop");
225
	XCTAssertEqualObjects(task.originalRequest.URL.path, @"/api/apps/app_id/product_listings.json");
226 227 228
	NSSet *requestQueryItems = [NSSet setWithArray:[task.originalRequest.URL.query componentsSeparatedByString:@"&"]];
	NSSet *queryItems = [NSSet setWithArray:@[@"collection_id=1", @"limit=25", @"page=1", @"sort_by=created-descending"]];
	XCTAssertEqualObjects(requestQueryItems, queryItems);
229 230 231 232
}

- (void)testProductsInCollectionWithSortOrderPriceAscending
{
233
	BUYRequestOperation *task = [self.client getProductsPage:1 inCollection:@1 sortOrder:BUYCollectionSortPriceAscending completion:^(NSArray<BUYProduct *> *products, NSUInteger page, BOOL reachedEnd, NSError *error) {}];
Rune Madsen committed
234
	XCTAssertEqualObjects(task.originalRequest.HTTPMethod, @"GET");
235 236
	XCTAssertEqualObjects(task.originalRequest.URL.scheme, @"https");
	XCTAssertEqualObjects(task.originalRequest.URL.host, @"test_shop");
237
	XCTAssertEqualObjects(task.originalRequest.URL.path, @"/api/apps/app_id/product_listings.json");
238 239 240
	NSSet *requestQueryItems = [NSSet setWithArray:[task.originalRequest.URL.query componentsSeparatedByString:@"&"]];
	NSSet *queryItems = [NSSet setWithArray:@[@"collection_id=1", @"limit=25", @"page=1", @"sort_by=price-ascending"]];
	XCTAssertEqualObjects(requestQueryItems, queryItems);
241 242 243 244
}

- (void)testProductsInCollectionWithSortOrderPriceDescending
{
245
	BUYRequestOperation *task = [self.client getProductsPage:1 inCollection:@1 sortOrder:BUYCollectionSortPriceDescending completion:^(NSArray<BUYProduct *> *products, NSUInteger page, BOOL reachedEnd, NSError *error) {}];
Rune Madsen committed
246
	XCTAssertEqualObjects(task.originalRequest.HTTPMethod, @"GET");
247 248
	XCTAssertEqualObjects(task.originalRequest.URL.scheme, @"https");
	XCTAssertEqualObjects(task.originalRequest.URL.host, @"test_shop");
249
	XCTAssertEqualObjects(task.originalRequest.URL.path, @"/api/apps/app_id/product_listings.json");
250 251 252
	NSSet *requestQueryItems = [NSSet setWithArray:[task.originalRequest.URL.query componentsSeparatedByString:@"&"]];
	NSSet *queryItems = [NSSet setWithArray:@[@"collection_id=1", @"limit=25", @"page=1", @"sort_by=price-descending"]];
	XCTAssertEqualObjects(requestQueryItems, queryItems);
253 254 255 256
}

- (void)testProductsInCollectionWithSortOrderTitleAscending
{
257
	BUYRequestOperation *task = [self.client getProductsPage:1 inCollection:@1 sortOrder:BUYCollectionSortTitleAscending completion:^(NSArray<BUYProduct *> *products, NSUInteger page, BOOL reachedEnd, NSError *error) {}];
Rune Madsen committed
258
	XCTAssertEqualObjects(task.originalRequest.HTTPMethod, @"GET");
259 260
	XCTAssertEqualObjects(task.originalRequest.URL.scheme, @"https");
	XCTAssertEqualObjects(task.originalRequest.URL.host, @"test_shop");
261
	XCTAssertEqualObjects(task.originalRequest.URL.path, @"/api/apps/app_id/product_listings.json");
262 263 264
	NSSet *requestQueryItems = [NSSet setWithArray:[task.originalRequest.URL.query componentsSeparatedByString:@"&"]];
	NSSet *queryItems = [NSSet setWithArray:@[@"collection_id=1", @"limit=25", @"page=1", @"sort_by=title-ascending"]];
	XCTAssertEqualObjects(requestQueryItems, queryItems);
265 266 267 268
}

- (void)testProductsInCollectionWithSortOrderTitleDescending
{
269
	BUYRequestOperation *task = [self.client getProductsPage:1 inCollection:@1 sortOrder:BUYCollectionSortTitleDescending completion:^(NSArray<BUYProduct *> *products, NSUInteger page, BOOL reachedEnd, NSError *error) {}];
Rune Madsen committed
270
	XCTAssertEqualObjects(task.originalRequest.HTTPMethod, @"GET");
271 272
	XCTAssertEqualObjects(task.originalRequest.URL.scheme, @"https");
	XCTAssertEqualObjects(task.originalRequest.URL.host, @"test_shop");
273
	XCTAssertEqualObjects(task.originalRequest.URL.path, @"/api/apps/app_id/product_listings.json");
274 275 276
	NSSet *requestQueryItems = [NSSet setWithArray:[task.originalRequest.URL.query componentsSeparatedByString:@"&"]];
	NSSet *queryItems = [NSSet setWithArray:@[@"collection_id=1", @"limit=25", @"page=1", @"sort_by=title-descending"]];
	XCTAssertEqualObjects(requestQueryItems, queryItems);
277 278
}

279 280 281 282 283
- (BUYCart *)cart
{
	return [self.client.modelManager insertCartWithJSONDictionary:nil];
}

284 285 286 287
#pragma mark - Customer Tests -

- (void)testCustomerCreationURL
{
288 289 290 291 292 293 294 295 296
	NSArray *items = @[
					   [BUYAccountCredentialItem itemWithFirstName:@"michael"],
					   [BUYAccountCredentialItem itemWithLastName:@"scott"],
					   [BUYAccountCredentialItem itemWithEmail:@"fake@example.com"],
					   [BUYAccountCredentialItem itemWithPassword:@"password"],
					   [BUYAccountCredentialItem itemWithPasswordConfirmation:@"password"],
					   ];
	BUYAccountCredentials *credentials = [BUYAccountCredentials credentialsWithItems:items];
	
297
	BUYRequestOperation *task = [self.client createCustomerWithCredentials:credentials callback:^(BUYCustomer *customer, NSString *token, NSError *error) {
298 299
		
	}];
300 301 302 303 304 305 306 307 308 309
	
	XCTAssertEqualObjects(task.originalRequest.URL.scheme, @"https");
	XCTAssertEqualObjects(task.originalRequest.URL.path, @"/api/customers.json");
	XCTAssertEqualObjects(task.originalRequest.HTTPMethod, @"POST");
	
	NSError *error = nil;
	NSDictionary *payload = [NSJSONSerialization JSONObjectWithData:task.originalRequest.HTTPBody options:0 error:&error];
	
	XCTAssertNil(error);
	NSDictionary *dict = @{@"customer": @{
310 311 312 313 314 315
								   @"first_name": @"michael",
								   @"last_name": @"scott",
								   @"email": @"fake@example.com",
								   @"password": @"password",
								   @"password_confirmation": @"password"
								   }};
316 317 318 319 320
	XCTAssertEqualObjects(payload, dict);
}

- (void)testLoginCustomerURL
{
321 322 323 324 325
	NSArray *items = @[
					   [BUYAccountCredentialItem itemWithEmail:@"fake@example.com"],
					   [BUYAccountCredentialItem itemWithPassword:@"password"],
					   ];
	BUYAccountCredentials *credentials = [BUYAccountCredentials credentialsWithItems:items];
326
	BUYRequestOperation *task = [self.client loginCustomerWithCredentials:credentials callback:^(BUYCustomer *customer, NSString *token, NSError *error) {
327 328
		
	}];
329 330 331 332 333 334 335 336 337
	
	XCTAssertEqualObjects(task.originalRequest.URL.scheme, @"https");
	XCTAssertEqualObjects(task.originalRequest.URL.path, @"/api/customers/customer_token.json");
	XCTAssertEqualObjects(task.originalRequest.HTTPMethod, @"POST");
	
	NSError *error = nil;
	NSDictionary *payload = [NSJSONSerialization JSONObjectWithData:task.originalRequest.HTTPBody options:0 error:&error];
	
	XCTAssertNil(error);
338 339 340 341
	NSDictionary *dict = @{@"customer": @{
								   @"email": @"fake@example.com",
								   @"password": @"password",
								   }};
342 343 344 345 346
	XCTAssertEqualObjects(payload, dict);
}

- (void)testGetCustomerURL
{
347
	BUYRequestOperation *task = [self.client getCustomerWithID:@"" callback:^(BUYCustomer *customer, NSError *error) {
348 349
		
	}];
350 351 352 353 354 355 356 357 358 359
	
	XCTAssertEqualObjects(task.originalRequest.URL.scheme, @"https");
	XCTAssertEqualObjects(task.originalRequest.URL.path, @"/api/customers.json");
	XCTAssertEqualObjects(task.originalRequest.HTTPMethod, @"GET");
	
	XCTAssertEqualObjects(self.client.customerToken, task.originalRequest.allHTTPHeaderFields[BUYClientCustomerAccessToken]);
}

- (void)testGetOrdersForCustomerURL
{
360
	BUYRequestOperation *task = [self.client getOrdersForCustomerWithCallback:^(NSArray<BUYOrder *> *orders, NSError *error) {
361 362
		
	}];
363 364 365 366 367 368 369 370 371 372 373
	
	XCTAssertEqualObjects(task.originalRequest.URL.scheme, @"https");
	XCTAssertEqualObjects(task.originalRequest.URL.path, @"/api/customers/orders.json");
	XCTAssertEqualObjects(task.originalRequest.HTTPMethod, @"GET");
	
	XCTAssertEqualObjects(self.client.customerToken, task.originalRequest.allHTTPHeaderFields[BUYClientCustomerAccessToken]);
}

- (void)testCustomerRecovery
{
	NSString *email = @"fake@example.com";
374
	BUYRequestOperation *task = [self.client recoverPasswordForCustomer:email callback:^(BUYStatus status, NSError *error) {
375 376
		
	}];
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393
	
	XCTAssertEqualObjects(task.originalRequest.URL.scheme, @"https");
	XCTAssertEqualObjects(task.originalRequest.URL.path, @"/api/customers/recover.json");
	XCTAssertEqualObjects(task.originalRequest.HTTPMethod, @"POST");
	
	NSError *error = nil;
	NSDictionary *payload = [NSJSONSerialization JSONObjectWithData:task.originalRequest.HTTPBody options:0 error:&error];
	
	XCTAssertNil(error);
	NSDictionary *dict = @{@"email": email};
	XCTAssertEqualObjects(payload, dict);
}

- (void)testTokenRenewal
{
	self.client.customerToken = nil;
	
394
	BUYRequestOperation *task = [self.client renewCustomerTokenWithID:@"" callback:^(NSString *token, NSError *error) {}];
395 396 397
	XCTAssertNil(task); // task should be nil if no customer token was set on the client
	
	self.client.customerToken = BUYFakeCustomerToken;
398
	task = [self.client renewCustomerTokenWithID:@"1" callback:^(NSString *token, NSError *error) {
399 400
		
	}];
401 402 403 404 405 406 407 408
	
	XCTAssertEqualObjects(task.originalRequest.URL.scheme, @"https");
	XCTAssertEqualObjects(task.originalRequest.URL.path, @"/api/customers/1/customer_token/renew.json");
	XCTAssertEqualObjects(task.originalRequest.HTTPMethod, @"PUT");
}

- (void)testCustomerActivation
{
409 410 411 412 413
	NSArray *items = @[
					   [BUYAccountCredentialItem itemWithPassword:@"12345"],
					   [BUYAccountCredentialItem itemWithPasswordConfirmation:@"12345"],
					   ];
	BUYAccountCredentials *credentials = [BUYAccountCredentials credentialsWithItems:items];
414
	NSString *customerID = @"12345";
415 416
	NSString *token      = @"12345";
	BUYRequestOperation *task = [self.client activateCustomerWithCredentials:credentials customerID:customerID token:token callback:^(BUYCustomer *customer, NSString *token, NSError *error) {
417 418
		
	}];
419 420 421 422 423 424
	
	XCTAssertEqualObjects(task.originalRequest.URL.scheme, @"https");
	XCTAssertEqualObjects(task.originalRequest.URL.path, @"/api/customers/12345/activate.json");
	XCTAssertEqualObjects(task.originalRequest.HTTPMethod, @"PUT");
}

425
@end