Completed
Push — master ( 39fd86...f4b36d )
by Stanislav
01:54
created

client.*MockedTogglSession.GetDetailedReport   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nop 4
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
package client
2
3
import (
4
	"bytes"
5
	"encoding/xml"
6
	"github.com/popstas/go-toggl"
7
	"github.com/popstas/planfix-go/planfix"
8
	"github.com/stretchr/testify/assert"
9
	"github.com/stretchr/testify/mock"
10
	"github.com/viasite/planfix-toggl-server/app/config"
11
	"io/ioutil"
12
	"log"
13
	"net/http"
14
	"net/http/httptest"
15
	"testing"
16
	"time"
17
)
18
19
var output bytes.Buffer
20
21
type planfixRequestStruct struct {
22
	XMLName xml.Name `xml:"request"`
23
	Method  string   `xml:"method,attr"`
24
	Account string   `xml:"account"`
25
	Sid     string   `xml:"sid"`
26
}
27
28
func fixtureFromFile(fixtureName string) string {
29
	buf, _ := ioutil.ReadFile("../../tests/fixtures/" + fixtureName)
30
	return string(buf)
31
}
32
33
type MockedServer struct {
34
	*httptest.Server
35
	Requests  [][]byte
36
	Responses []string // fifo queue of answers
37
}
38
39
func NewMockedServer(responses []string) *MockedServer {
40
	s := &MockedServer{
41
		Requests:  [][]byte{},
42
		Responses: responses,
43
	}
44
45
	s.Server = httptest.NewServer(s)
46
	return s
47
}
48
49
func (s *MockedServer) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
50
	lastRequest, err := ioutil.ReadAll(req.Body)
51
	if err != nil {
52
		panic(err)
53
	}
54
55
	rs := planfixRequestStruct{}
56
	err = xml.Unmarshal(lastRequest, &rs)
57
	if err != nil {
58
		panic(err)
59
	}
60
	s.Requests = append(s.Requests, lastRequest)
61
	answer := s.Responses[0]
62
63
	s.Responses = s.Responses[1:]
64
	resp.Write([]byte(answer))
65
}
66
67
type MockedTogglSession struct {
68
	mock.Mock
69
	TogglSession
70
}
71
72
func (s *MockedTogglSession) GetAccount() (toggl.Account, error) {
73
	args := s.Called()
74
	return args.Get(0).(toggl.Account), args.Error(1)
75
}
76
77
func (s *MockedTogglSession) GetDetailedReport(workspace int, since, until string, page int) (toggl.DetailedReport, error) {
78
	args := s.Called(workspace, since, until, page)
79
	return args.Get(0).(toggl.DetailedReport), args.Error(1)
80
}
81
82
func (s *MockedTogglSession) AddRemoveTag(entryID int, tag string, add bool) (toggl.TimeEntry, error) {
83
	args := s.Called(entryID, tag, add)
84
	return args.Get(0).(toggl.TimeEntry), args.Error(1)
85
}
86
87
func newClient() TogglClient {
88
	cfg := config.Config{
89
		TogglSentTag: "sent",
90
	}
91
	api := planfix.New("", "apiKey", "account", "user", "password")
92
	api.Sid = "123"
93
94
	sess := &MockedTogglSession{}
95
	return TogglClient{
96
		Session:    sess,
97
		Config:     &cfg,
98
		PlanfixAPI: api,
99
		Logger:     log.New(&output, "", log.LstdFlags),
100
	}
101
}
102
103
func getTestEntries() []TogglPlanfixEntry {
104
	date := getTestDate()
105
	return []TogglPlanfixEntry{
106
		{
107
			toggl.DetailedTimeEntry{Duration: 1, Start: &date},
108
			PlanfixEntryData{TaskID: 1, GroupCount: 1},
109
		},
110
		{
111
			toggl.DetailedTimeEntry{Duration: 2, Start: &date},
112
			PlanfixEntryData{TaskID: 1, GroupCount: 1},
113
		},
114
		{
115
			toggl.DetailedTimeEntry{Duration: 3, Start: &date},
116
			PlanfixEntryData{TaskID: 2, GroupCount: 1},
117
		},
118
		{
119
			toggl.DetailedTimeEntry{Duration: 4, Start: &date},
120
			PlanfixEntryData{TaskID: 2, GroupCount: 1},
121
		},
122
		{
123
			toggl.DetailedTimeEntry{Duration: 5, Start: &date},
124
			PlanfixEntryData{TaskID: 2, GroupCount: 1},
125
		},
126
		{
127
			toggl.DetailedTimeEntry{Duration: 6, Start: &date},
128
			PlanfixEntryData{TaskID: 3, GroupCount: 1},
129
		},
130
	}
131
}
132
133
func getTestGroupedEntries() map[int][]TogglPlanfixEntry {
134
	date := getTestDate()
135
	return map[int][]TogglPlanfixEntry{
136
		1: {
137
			{
138
				toggl.DetailedTimeEntry{Duration: 1, Start: &date},
139
				PlanfixEntryData{TaskID: 1, GroupCount: 1},
140
			},
141
			{
142
				toggl.DetailedTimeEntry{Duration: 2, Start: &date},
143
				PlanfixEntryData{TaskID: 1, GroupCount: 1},
144
			},
145
		},
146
		2: {
147
			{
148
				toggl.DetailedTimeEntry{Duration: 3, Start: &date},
149
				PlanfixEntryData{TaskID: 2, GroupCount: 1},
150
			},
151
			{
152
				toggl.DetailedTimeEntry{Duration: 4, Start: &date},
153
				PlanfixEntryData{TaskID: 2, GroupCount: 1},
154
			},
155
			{
156
				toggl.DetailedTimeEntry{Duration: 5, Start: &date},
157
				PlanfixEntryData{TaskID: 2, GroupCount: 1},
158
			},
159
		},
160
		3: {
161
			{
162
				toggl.DetailedTimeEntry{Duration: 6, Start: &date},
163
				PlanfixEntryData{TaskID: 3, GroupCount: 1},
164
			},
165
		},
166
	}
167
}
168
169
func getTestDayEntries() map[string][]TogglPlanfixEntry {
170
	date := getTestDate()
171
	return map[string][]TogglPlanfixEntry{
172
		date.Format("02-01-2006"): {
173
			{
174
				toggl.DetailedTimeEntry{Duration: 1, Start: &date},
175
				PlanfixEntryData{TaskID: 1, GroupCount: 1},
176
			},
177
			{
178
				toggl.DetailedTimeEntry{Duration: 2, Start: &date},
179
				PlanfixEntryData{TaskID: 1, GroupCount: 1},
180
			},
181
			{
182
				toggl.DetailedTimeEntry{Duration: 3, Start: &date},
183
				PlanfixEntryData{TaskID: 2, GroupCount: 1},
184
			},
185
			{
186
				toggl.DetailedTimeEntry{Duration: 4, Start: &date},
187
				PlanfixEntryData{TaskID: 2, GroupCount: 1},
188
			},
189
			{
190
				toggl.DetailedTimeEntry{Duration: 5, Start: &date},
191
				PlanfixEntryData{TaskID: 2, GroupCount: 1},
192
			},
193
			{
194
				toggl.DetailedTimeEntry{Duration: 6, Start: &date},
195
				PlanfixEntryData{TaskID: 3, GroupCount: 1},
196
			},
197
		},
198
	}
199
}
200
201
func getTestDate() time.Time {
202
	return time.Date(2018, 3, 4, 1, 2, 3, 0, time.Local)
203
}
204
205
func getTestDetailedReport() toggl.DetailedReport {
206
	date := getTestDate()
207
	return toggl.DetailedReport{Data: []toggl.DetailedTimeEntry{
208
		// will be filtered by sent tag
209
		{
210
			ID:          1,
211
			Project:     "project1",
212
			Description: "description1",
213
			Tags:        []string{"12345", "sent"},
214
			Start:       &date,
215
		},
216
		{
217
			ID:          2,
218
			Project:     "project1",
219
			Description: "description1",
220
			Tags:        []string{"12345"},
221
			Start:       &date,
222
		},
223
		// will be filtered by taskID tag
224
		{
225
			ID:          3,
226
			Project:     "project1",
227
			Description: "description1",
228
			Tags:        []string{},
229
			Start:       &date,
230
		},
231
		{
232
			ID:          4,
233
			Project:     "project1",
234
			Description: "description1",
235
			Tags:        []string{"12345"},
236
			Start:       &date,
237
		},
238
	}}
239
}
240
241
func TestTogglClient_SumEntriesGroup(t *testing.T) {
242
	date := getTestDate()
243
	c := newClient()
244
	groupedEntries := getTestGroupedEntries()
245
	expected := []TogglPlanfixEntry{
246
		{
247
			toggl.DetailedTimeEntry{Duration: 3, Start: &date},
248
			PlanfixEntryData{TaskID: 1, GroupCount: 2},
249
		},
250
		{
251
			toggl.DetailedTimeEntry{Duration: 12, Start: &date},
252
			PlanfixEntryData{TaskID: 2, GroupCount: 3},
253
		},
254
		{
255
			toggl.DetailedTimeEntry{Duration: 6, Start: &date},
256
			PlanfixEntryData{TaskID: 3, GroupCount: 1},
257
		},
258
	}
259
260
	summed := c.SumEntriesGroup(groupedEntries)
261
	assert.Equal(t, expected, summed)
262
}
263
264
func TestTogglClient_GroupEntriesByTask(t *testing.T) {
265
	c := newClient()
266
	entries := getTestEntries()
267
	expected := getTestGroupedEntries()
268
269
	grouped := c.GroupEntriesByTask(entries)
270
	assert.Equal(t, expected, grouped)
271
}
272
273
func TestTogglClient_GroupEntriesByDay(t *testing.T) {
274
	c := newClient()
275
	entries := getTestEntries()
276
	expected := getTestDayEntries()
277
278
	grouped := c.GroupEntriesByDay(entries)
279
	assert.Equal(t, expected, grouped)
280
}
281
282
func TestTogglClient_GroupEntriesByTask_empty(t *testing.T) {
283
	c := newClient()
284
	entries := []TogglPlanfixEntry{}
285
	expected := map[int][]TogglPlanfixEntry{}
286
287
	grouped := c.GroupEntriesByTask(entries)
288
	assert.Equal(t, expected, grouped)
289
}
290
291
func TestTogglClient_sendEntries_dryRun(t *testing.T) {
292
	c := newClient()
293
	c.Config.DryRun = true
294
	date := getTestDate()
295
	entries := []TogglPlanfixEntry{
296
		{
297
			toggl.DetailedTimeEntry{
298
				Duration:    60000,
299
				Start:       &date,
300
				Project:     "project",
301
				Description: "description",
302
			},
303
			PlanfixEntryData{TaskID: 1, GroupCount: 1},
304
		},
305
		{
306
			toggl.DetailedTimeEntry{Duration: 120000},
307
			PlanfixEntryData{TaskID: 1, GroupCount: 1},
308
		},
309
	}
310
311
	c.sendEntries(1, entries)
312
	assert.Contains(t, output.String(), "[DEBUG] sending [project]") //  description (3)
313
	assert.Contains(t, output.String(), "[DEBUG] dry-run")
314
}
315
316
func TestTogglClient_GetTogglUserID(t *testing.T) {
317
	c := newClient()
318
	sess := &MockedTogglSession{}
319
	c.Session = sess
320
321
	returnedTogglUser := toggl.Account{Data: struct {
322
		APIToken        string            `json:"api_token"`
323
		Timezone        string            `json:"timezone"`
324
		ID              int               `json:"id"`
325
		Workspaces      []toggl.Workspace `json:"workspaces"`
326
		Clients         []toggl.Client    `json:"clients"`
327
		Projects        []toggl.Project   `json:"projects"`
328
		Tasks           []toggl.Task      `json:"tasks"`
329
		Tags            []toggl.Tag       `json:"tags"`
330
		TimeEntries     []toggl.TimeEntry `json:"time_entries"`
331
		BeginningOfWeek int               `json:"beginning_of_week"`
332
	}{ID: 123}}
333
	sess.On("GetAccount").Return(returnedTogglUser, nil)
334
335
	togglUser, _ := c.GetTogglUser()
336
	assert.Equal(t, 123, togglUser.Data.ID)
337
}
338
339
func TestTogglClient_GetPlanfixUser(t *testing.T) {
340
	c := newClient()
341
	ms := NewMockedServer([]string{
342
		fixtureFromFile("user.get.xml"),
343
		//fixtureFromFile("error.xml"),
344
	})
345
	c.PlanfixAPI.URL = ms.URL
346
347
	planfixUser, _ := c.GetPlanfixUser()
348
	assert.Equal(t, 9230, planfixUser.ID)
349
}
350
351
func TestTogglClient_GetEntries(t *testing.T) {
352
	c := newClient()
353
	sess := &MockedTogglSession{}
354
	c.Session = sess
355
356
	since := time.Now().AddDate(0, 0, -30).Format("2006-01-02")
357
	until := time.Now().AddDate(0, 0, 1).Format("2006-01-02")
358
	report := toggl.DetailedReport{Data: []toggl.DetailedTimeEntry{
359
		{
360
			ID:          1,
361
			Project:     "project1",
362
			Description: "description1",
363
			Tags:        []string{"12345", "sent"},
364
		},
365
	}}
366
367
	sess.On("GetDetailedReport", 234, since, until, 1).Return(report, nil)
368
369
	report, _ = c.Session.GetDetailedReport(234, since, until, 1)
370
	entries, _ := c.GetEntries(234, since, until)
371
372
	expected := []TogglPlanfixEntry{
373
		{
374
			DetailedTimeEntry: report.Data[0],
375
			Planfix:           PlanfixEntryData{GroupCount: 1, Sent: true, TaskID: 12345},
376
		},
377
	}
378
	assert.Equal(t, expected, entries)
379
}
380
381
/*func TestTogglClient_GetPendingEntries(t *testing.T) {
382
	c := newClient()
383
	sess := &MockedTogglSession{}
384
	c.Session = sess
385
386
	date := getTestDate()
387
	report := getTestDetailedReport()
388
389
	rp := toggl.DetailedReportParams{
390
		WorkspaceID: c.Config.TogglWorkspaceID,
391
		Rounding: true,
392
		Since: getTestDate(),
393
		Until: getTestDate(),
394
	}
395
	// не понимаю, как замокать передачу объекта, падает с паникой
396
	sess.On("GetDetailedReportV2", rp).Return(report, nil)
397
398
	entries, _ := c.GetPendingEntries()
399
400
	expected := []TogglPlanfixEntry{
401
		{
402
			DetailedTimeEntry: toggl.DetailedTimeEntry{
403
				ID:          2,
404
				Project:     "project1",
405
				Description: "description1",
406
				Tags:        []string{"12345"},
407
				Start:       &date,
408
			},
409
			Planfix: PlanfixEntryData{GroupCount: 1, Sent: false, TaskID: 12345},
410
		},
411
		{
412
			DetailedTimeEntry: toggl.DetailedTimeEntry{
413
				ID:          4,
414
				Project:     "project1",
415
				Description: "description1",
416
				Tags:        []string{"12345"},
417
				Start:       &date,
418
			},
419
			Planfix: PlanfixEntryData{GroupCount: 1, Sent: false, TaskID: 12345},
420
		},
421
	}
422
	assert.Equal(t, expected, entries)
423
}*/
424
425
func TestTogglClient_markAsSent(t *testing.T) {
426
	c := newClient()
427
	sess := &MockedTogglSession{}
428
	c.Session = sess
429
430
	planfixEntries := []TogglPlanfixEntry{
431
		{
432
			DetailedTimeEntry: toggl.DetailedTimeEntry{
433
				ID:          2,
434
				Project:     "project1",
435
				Description: "description1",
436
				Tags:        []string{"12345"},
437
			},
438
			Planfix: PlanfixEntryData{GroupCount: 1, Sent: false, TaskID: 12345},
439
		},
440
		{
441
			DetailedTimeEntry: toggl.DetailedTimeEntry{
442
				ID:          4,
443
				Project:     "project1",
444
				Description: "description1",
445
				Tags:        []string{"12345"},
446
			},
447
			Planfix: PlanfixEntryData{GroupCount: 1, Sent: false, TaskID: 12345},
448
		},
449
	}
450
	togglEntries := []toggl.TimeEntry{
451
		{
452
			ID:          2,
453
			Description: "description1",
454
			Tags:        []string{"12345"},
455
		},
456
		{
457
			ID:          4,
458
			Description: "description1",
459
			Tags:        []string{"12345"},
460
		},
461
	}
462
	sess.On("AddRemoveTag", 2, c.Config.TogglSentTag, true).Return(togglEntries[0], nil)
463
	sess.On("AddRemoveTag", 4, c.Config.TogglSentTag, true).Return(togglEntries[1], nil)
464
465
	err := c.markAsSent(planfixEntries)
466
	assert.NoError(t, err)
467
}
468
469
func TestTogglClient_getTaskEmail(t *testing.T) {
470
	c := newClient()
471
	c.Config.PlanfixAccount = "mycompany"
472
473
	taskEmail := c.getTaskEmail(123)
474
	assert.Equal(t, "[email protected]", taskEmail)
475
}
476
477
func TestTogglClient_getEmailBody(t *testing.T) {
478
	c := newClient()
479
	c.Config.PlanfixAccount = "mycompany"
480
	c.Config.SMTPEmailFrom = "[email protected]"
481
	c.Config.PlanfixAnaliticTypeValue = "Название аналитики"
482
	c.Config.PlanfixAuthorName = "Имя Фамилия"
483
484
	expectedBody := "Content-Type: text/plain; charset=\"utf-8\"\r\n" +
485
		"From: [email protected]\r\n" +
486
		"To: [email protected]\r\n" +
487
		"Subject: @toggl @nonotify\r\n" +
488
		"\r\n" +
489
		"Вид работы: Название аналитики\r\n" +
490
		"time: 234\r\n" +
491
		"Автор: Имя Фамилия\r\n" +
492
		"Дата: 2018-03-04\r\n"
493
494
	body := c.getEmailBody(123, "2018-03-04", 234)
495
	assert.Equal(t, expectedBody, body)
496
}
497
498
func TestTogglClient_GetAnaliticData(t *testing.T) {
499
	c := newClient()
500
	ms := NewMockedServer([]string{
501
		fixtureFromFile("analitic.getList.xml"),
502
		fixtureFromFile("analitic.getOptions.xml"),
503
		fixtureFromFile("analitic.getHandbook.xml"),
504
	})
505
	c.PlanfixAPI.URL = ms.URL
506
	c.Config.PlanfixUserID = 123
507
	c.Config.PlanfixAnaliticName = "Выработка"
508
	c.Config.PlanfixAnaliticTypeName = "Вид работы"
509
	c.Config.PlanfixAnaliticTypeValue = "Поминутная работа программиста"
510
	c.Config.PlanfixAnaliticCountName = "Кол-во"
511
	c.Config.PlanfixAnaliticCommentName = "Комментарий / ссылка"
512
	c.Config.PlanfixAnaliticDateName = "Дата"
513
	c.Config.PlanfixAnaliticUsersName = "Сотрудник"
514
515
	// нормальное поведение
516
	analiticData, err := c.GetAnaliticDataCached(
517
		c.Config.PlanfixAnaliticName,
518
		c.Config.PlanfixAnaliticTypeName,
519
		c.Config.PlanfixAnaliticTypeValue,
520
		c.Config.PlanfixAnaliticCountName,
521
		c.Config.PlanfixAnaliticCommentName,
522
		c.Config.PlanfixAnaliticDateName,
523
		c.Config.PlanfixAnaliticUsersName,
524
	)
525
	assert.NoError(t, err)
526
	assert.Equal(t, 725, analiticData.TypeValueID)
527
528
	// тест кеша
529
	analiticData, err = c.GetAnaliticDataCached("", "", "", "", "", "", "")
530
	assert.NoError(t, err)
531
	assert.Equal(t, 725, analiticData.TypeValueID)
532
533
	// неправильный вид работы
534
	c.analiticData = PlanfixAnaliticData{} // сброс кеша
535
	ms = NewMockedServer([]string{
536
		fixtureFromFile("analitic.getList.xml"),
537
		fixtureFromFile("analitic.getOptions.xml"),
538
		fixtureFromFile("analitic.getHandbook.xml"),
539
	})
540
	c.PlanfixAPI.URL = ms.URL
541
	c.Config.PlanfixAnaliticTypeValue = "Какой-то неизвестный вид работы"
542
	analiticData, err = c.GetAnaliticData(
543
		c.Config.PlanfixAnaliticName,
544
		c.Config.PlanfixAnaliticTypeName,
545
		c.Config.PlanfixAnaliticTypeValue,
546
		c.Config.PlanfixAnaliticCountName,
547
		c.Config.PlanfixAnaliticCommentName,
548
		c.Config.PlanfixAnaliticDateName,
549
		c.Config.PlanfixAnaliticUsersName,
550
	)
551
	assert.Error(t, err)
552
	assert.Equal(t, 0, analiticData.TypeValueID)
553
	assert.Equal(t, 749, analiticData.CommentID)
554
}
555
556
// TODO: проходит метод полностью, но непонятно что проверяет
557
func TestTogglClient_sendWithPlanfixAPI(t *testing.T) {
558
	c := newClient()
559
	c.Config.PlanfixAnaliticName = "Выработка"
560
	c.Config.PlanfixAnaliticTypeName = "Вид работы"
561
	c.Config.PlanfixAnaliticTypeValue = "Поминутная работа программиста"
562
	c.Config.PlanfixAnaliticCountName = "Кол-во"
563
	c.Config.PlanfixAnaliticCommentName = "Комментарий / ссылка"
564
	c.Config.PlanfixAnaliticDateName = "Дата"
565
	c.Config.PlanfixAnaliticUsersName = "Сотрудник"
566
567
	ms := NewMockedServer([]string{
568
		fixtureFromFile("analitic.getList.xml"),
569
		fixtureFromFile("analitic.getOptions.xml"),
570
		fixtureFromFile("analitic.getHandbook.xml"),
571
		fixtureFromFile("action.add.xml"),
572
	})
573
	c.PlanfixAPI.URL = ms.URL
574
	c.Config.PlanfixUserID = 123
575
	c.Config.PlanfixAnaliticName = "Выработка"
576
577
	err := c.sendWithPlanfixAPI(123, "2018-03-04", 234, "comment")
578
	assert.NoError(t, err)
579
}
580
581
// TODO: проходит метод полностью, но непонятно что проверяет
582
/*func TestTogglClient_SendToPlanfix(t *testing.T) {
583
	c := newClient()
584
	sess := &MockedTogglSession{}
585
	c.Session = sess
586
587
	c.Config.PlanfixAnaliticName = "Выработка"
588
	c.Config.PlanfixAnaliticTypeName = "Вид работы"
589
	c.Config.PlanfixAnaliticTypeValue = "Поминутная работа программиста"
590
	c.Config.PlanfixAnaliticCountName = "Кол-во"
591
	c.Config.PlanfixAnaliticCommentName = "Комментарий / ссылка"
592
	c.Config.PlanfixAnaliticDateName = "Дата"
593
	c.Config.PlanfixAnaliticUsersName = "Сотрудник"
594
595
	ms := NewMockedServer([]string{
596
		fixtureFromFile("analitic.getList.xml"),
597
		fixtureFromFile("analitic.getOptions.xml"),
598
		fixtureFromFile("analitic.getHandbook.xml"),
599
		fixtureFromFile("action.add.xml"),
600
	})
601
	c.PlanfixAPI.URL = ms.URL
602
	c.Config.PlanfixUserID = 123
603
	c.Config.PlanfixAnaliticName = "Выработка"
604
605
	since := time.Now().AddDate(0, 0, -30).Format("2006-01-02")
606
	until := time.Now().AddDate(0, 0, 1).Format("2006-01-02")
607
	report := getTestDetailedReport()
608
	// тут падает с паникой, см. тест про PendingEntries
609
	sess.On("GetDetailedReport", c.Config.TogglWorkspaceID, since, until, 1).Return(report, nil)
610
611
	sess.On("AddRemoveTag", 2, c.Config.TogglSentTag, true).Return(toggl.TimeEntry{}, nil)
612
	sess.On("AddRemoveTag", 4, c.Config.TogglSentTag, true).Return(toggl.TimeEntry{}, nil)
613
614
	pending, _ := c.GetPendingEntries()
615
	grouped := c.GroupEntriesByTask(pending)
616
	summedExpected := c.SumEntriesGroup(grouped)
617
618
	summed, err := c.SendToPlanfix()
619
	assert.NoError(t, err)
620
	assert.Equal(t, summedExpected, summed)
621
}*/
622