opensoma 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package.json +5 -1
- package/dist/src/agent-browser-launcher.d.ts +43 -0
- package/dist/src/agent-browser-launcher.d.ts.map +1 -0
- package/dist/src/agent-browser-launcher.js +97 -0
- package/dist/src/agent-browser-launcher.js.map +1 -0
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +8 -5
- package/dist/src/cli.js.map +1 -1
- package/dist/src/client.d.ts +34 -7
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/client.js +224 -52
- package/dist/src/client.js.map +1 -1
- package/dist/src/commands/agent-browser.d.ts +3 -0
- package/dist/src/commands/agent-browser.d.ts.map +1 -0
- package/dist/src/commands/agent-browser.js +27 -0
- package/dist/src/commands/agent-browser.js.map +1 -0
- package/dist/src/commands/auth.d.ts +1 -1
- package/dist/src/commands/auth.d.ts.map +1 -1
- package/dist/src/commands/auth.js +4 -2
- package/dist/src/commands/auth.js.map +1 -1
- package/dist/src/commands/dashboard.d.ts +13 -0
- package/dist/src/commands/dashboard.d.ts.map +1 -1
- package/dist/src/commands/dashboard.js +10 -18
- package/dist/src/commands/dashboard.js.map +1 -1
- package/dist/src/commands/helpers.d.ts +1 -1
- package/dist/src/commands/helpers.d.ts.map +1 -1
- package/dist/src/commands/helpers.js +2 -2
- package/dist/src/commands/helpers.js.map +1 -1
- package/dist/src/commands/index.d.ts +3 -1
- package/dist/src/commands/index.d.ts.map +1 -1
- package/dist/src/commands/index.js +3 -1
- package/dist/src/commands/index.js.map +1 -1
- package/dist/src/commands/mentoring.d.ts.map +1 -1
- package/dist/src/commands/mentoring.js +54 -29
- package/dist/src/commands/mentoring.js.map +1 -1
- package/dist/src/commands/notice.d.ts.map +1 -1
- package/dist/src/commands/notice.js +2 -1
- package/dist/src/commands/notice.js.map +1 -1
- package/dist/src/commands/report.d.ts.map +1 -1
- package/dist/src/commands/report.js +4 -2
- package/dist/src/commands/report.js.map +1 -1
- package/dist/src/commands/room.d.ts.map +1 -1
- package/dist/src/commands/room.js +125 -2
- package/dist/src/commands/room.js.map +1 -1
- package/dist/src/commands/schedule.d.ts +3 -0
- package/dist/src/commands/schedule.d.ts.map +1 -0
- package/dist/src/commands/schedule.js +27 -0
- package/dist/src/commands/schedule.js.map +1 -0
- package/dist/src/commands/team.d.ts.map +1 -1
- package/dist/src/commands/team.js +55 -4
- package/dist/src/commands/team.js.map +1 -1
- package/dist/src/commands/toz.d.ts +16 -0
- package/dist/src/commands/toz.d.ts.map +1 -0
- package/dist/src/commands/toz.js +488 -0
- package/dist/src/commands/toz.js.map +1 -0
- package/dist/src/constants.d.ts +5 -5
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +20 -8
- package/dist/src/constants.js.map +1 -1
- package/dist/src/credential-manager.d.ts +15 -0
- package/dist/src/credential-manager.d.ts.map +1 -1
- package/dist/src/credential-manager.js +46 -0
- package/dist/src/credential-manager.js.map +1 -1
- package/dist/src/formatters.d.ts +11 -3
- package/dist/src/formatters.d.ts.map +1 -1
- package/dist/src/formatters.js +281 -52
- package/dist/src/formatters.js.map +1 -1
- package/dist/src/http.d.ts +8 -0
- package/dist/src/http.d.ts.map +1 -1
- package/dist/src/http.js +29 -1
- package/dist/src/http.js.map +1 -1
- package/dist/src/index.d.ts +8 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/session-recovery.js +2 -0
- package/dist/src/session-recovery.js.map +1 -1
- package/dist/src/shared/utils/swmaestro.d.ts +34 -1
- package/dist/src/shared/utils/swmaestro.d.ts.map +1 -1
- package/dist/src/shared/utils/swmaestro.js +102 -39
- package/dist/src/shared/utils/swmaestro.js.map +1 -1
- package/dist/src/shared/utils/team-action-params.d.ts +3 -0
- package/dist/src/shared/utils/team-action-params.d.ts.map +1 -0
- package/dist/src/shared/utils/team-action-params.js +10 -0
- package/dist/src/shared/utils/team-action-params.js.map +1 -0
- package/dist/src/shared/utils/team-params.d.ts +12 -0
- package/dist/src/shared/utils/team-params.d.ts.map +1 -0
- package/dist/src/shared/utils/team-params.js +38 -0
- package/dist/src/shared/utils/team-params.js.map +1 -0
- package/dist/src/toz-client.d.ts +89 -0
- package/dist/src/toz-client.d.ts.map +1 -0
- package/dist/src/toz-client.js +204 -0
- package/dist/src/toz-client.js.map +1 -0
- package/dist/src/toz-pending-store.d.ts +30 -0
- package/dist/src/toz-pending-store.d.ts.map +1 -0
- package/dist/src/toz-pending-store.js +36 -0
- package/dist/src/toz-pending-store.js.map +1 -0
- package/dist/src/types.d.ts +147 -10
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +74 -6
- package/dist/src/types.js.map +1 -1
- package/package.json +5 -1
- package/src/agent-browser-launcher.test.ts +263 -0
- package/src/agent-browser-launcher.ts +159 -0
- package/src/cli.ts +10 -5
- package/src/client.test.ts +673 -30
- package/src/client.ts +287 -67
- package/src/commands/agent-browser.ts +33 -0
- package/src/commands/auth.test.ts +77 -26
- package/src/commands/auth.ts +5 -3
- package/src/commands/dashboard.test.ts +57 -0
- package/src/commands/dashboard.ts +22 -19
- package/src/commands/helpers.test.ts +76 -25
- package/src/commands/helpers.ts +3 -3
- package/src/commands/index.ts +3 -1
- package/src/commands/mentoring.ts +60 -29
- package/src/commands/notice.ts +2 -1
- package/src/commands/report.ts +4 -2
- package/src/commands/room.ts +160 -1
- package/src/commands/schedule.ts +32 -0
- package/src/commands/team.ts +73 -5
- package/src/commands/toz.test.ts +51 -0
- package/src/commands/toz.ts +607 -0
- package/src/constants.ts +20 -8
- package/src/credential-manager.test.ts +98 -0
- package/src/credential-manager.ts +50 -0
- package/src/formatters.test.ts +528 -33
- package/src/formatters.ts +309 -55
- package/src/http.test.ts +71 -2
- package/src/http.ts +41 -2
- package/src/index.ts +23 -1
- package/src/session-recovery.ts +2 -0
- package/src/shared/utils/swmaestro.test.ts +245 -9
- package/src/shared/utils/swmaestro.ts +150 -47
- package/src/shared/utils/team-action-params.test.ts +32 -0
- package/src/shared/utils/team-action-params.ts +10 -0
- package/src/shared/utils/team-params.test.ts +141 -0
- package/src/shared/utils/team-params.ts +53 -0
- package/src/toz-client.test.ts +243 -0
- package/src/toz-client.ts +311 -0
- package/src/toz-pending-store.test.ts +91 -0
- package/src/toz-pending-store.ts +62 -0
- package/src/types.test.ts +26 -13
- package/src/types.ts +87 -7
- package/dist/src/commands/event.d.ts +0 -3
- package/dist/src/commands/event.d.ts.map +0 -1
- package/dist/src/commands/event.js +0 -58
- package/dist/src/commands/event.js.map +0 -1
- package/src/commands/event.ts +0 -73
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { MENU_NO } from '../../constants'
|
|
4
|
+
import { buildTeamListParams, parseTeamSearchQuery } from './team-params'
|
|
5
|
+
|
|
6
|
+
describe('parseTeamSearchQuery', () => {
|
|
7
|
+
it('defaults plain text to an all-fields search', () => {
|
|
8
|
+
expect(parseTeamSearchQuery('전수열')).toEqual({ field: 'all', value: '전수열' })
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('parses the "team:" prefix as a team-name search', () => {
|
|
12
|
+
expect(parseTeamSearchQuery('team:오픈소마')).toEqual({ field: 'team', value: '오픈소마' })
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('parses the "member:" prefix as a member-name search', () => {
|
|
16
|
+
expect(parseTeamSearchQuery('member:김철수')).toEqual({ field: 'member', value: '김철수' })
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('parses the "mentor:" prefix as a mentor-name search', () => {
|
|
20
|
+
expect(parseTeamSearchQuery('mentor:전수열')).toEqual({ field: 'mentor', value: '전수열' })
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('parses the "project:" prefix as a project-name search', () => {
|
|
24
|
+
expect(parseTeamSearchQuery('project:Previzion')).toEqual({ field: 'project', value: 'Previzion' })
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('parses the "all:" prefix as an all-fields search', () => {
|
|
28
|
+
expect(parseTeamSearchQuery('all:전수열')).toEqual({ field: 'all', value: '전수열' })
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('sets the "me" flag when parsing "mentor:@me"', () => {
|
|
32
|
+
expect(parseTeamSearchQuery('mentor:@me')).toEqual({ field: 'mentor', value: '@me', me: true })
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('sets the "me" flag when parsing "member:@me"', () => {
|
|
36
|
+
expect(parseTeamSearchQuery('member:@me')).toEqual({ field: 'member', value: '@me', me: true })
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('does not set the "me" flag for "team:@me" or "project:@me"', () => {
|
|
40
|
+
expect(parseTeamSearchQuery('team:@me')).toEqual({ field: 'team', value: '@me' })
|
|
41
|
+
expect(parseTeamSearchQuery('project:@me')).toEqual({ field: 'project', value: '@me' })
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('treats an unknown prefix as a plain all-fields search', () => {
|
|
45
|
+
expect(parseTeamSearchQuery('foo:bar')).toEqual({ field: 'all', value: 'foo:bar' })
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('preserves everything after the first colon when the value contains colons', () => {
|
|
49
|
+
expect(parseTeamSearchQuery('team:foo:bar')).toEqual({ field: 'team', value: 'foo:bar' })
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
describe('buildTeamListParams', () => {
|
|
54
|
+
it('returns only menuNo when no options are passed', () => {
|
|
55
|
+
expect(buildTeamListParams()).toEqual({ menuNo: MENU_NO.TEAM })
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('omits search params when search is not provided', () => {
|
|
59
|
+
expect(buildTeamListParams({})).toEqual({ menuNo: MENU_NO.TEAM })
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('sets searchCnd="" for an all-fields search', () => {
|
|
63
|
+
expect(buildTeamListParams({ search: { field: 'all', value: '전수열' } })).toEqual({
|
|
64
|
+
menuNo: MENU_NO.TEAM,
|
|
65
|
+
searchCnd: '',
|
|
66
|
+
searchWrd: '전수열',
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('sets searchCnd=1 for a member-name search', () => {
|
|
71
|
+
expect(buildTeamListParams({ search: { field: 'member', value: '김철수' } })).toEqual({
|
|
72
|
+
menuNo: MENU_NO.TEAM,
|
|
73
|
+
searchCnd: '1',
|
|
74
|
+
searchWrd: '김철수',
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('sets searchCnd=2 for a mentor-name search', () => {
|
|
79
|
+
expect(buildTeamListParams({ search: { field: 'mentor', value: '전수열' } })).toEqual({
|
|
80
|
+
menuNo: MENU_NO.TEAM,
|
|
81
|
+
searchCnd: '2',
|
|
82
|
+
searchWrd: '전수열',
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('sets searchCnd=3 for a project-name search', () => {
|
|
87
|
+
expect(buildTeamListParams({ search: { field: 'project', value: 'Previzion' } })).toEqual({
|
|
88
|
+
menuNo: MENU_NO.TEAM,
|
|
89
|
+
searchCnd: '3',
|
|
90
|
+
searchWrd: 'Previzion',
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('sets searchCnd=4 for a team-name search', () => {
|
|
95
|
+
expect(buildTeamListParams({ search: { field: 'team', value: '오픈소마' } })).toEqual({
|
|
96
|
+
menuNo: MENU_NO.TEAM,
|
|
97
|
+
searchCnd: '4',
|
|
98
|
+
searchWrd: '오픈소마',
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('substitutes the current user name when "mentor:@me" is used', () => {
|
|
103
|
+
expect(
|
|
104
|
+
buildTeamListParams({
|
|
105
|
+
search: { field: 'mentor', value: '@me', me: true },
|
|
106
|
+
user: { userId: 'neo@example.com', userNm: '전수열' },
|
|
107
|
+
}),
|
|
108
|
+
).toEqual({
|
|
109
|
+
menuNo: MENU_NO.TEAM,
|
|
110
|
+
searchCnd: '2',
|
|
111
|
+
searchWrd: '전수열',
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('substitutes the current user name when "member:@me" is used', () => {
|
|
116
|
+
expect(
|
|
117
|
+
buildTeamListParams({
|
|
118
|
+
search: { field: 'member', value: '@me', me: true },
|
|
119
|
+
user: { userId: 'neo@example.com', userNm: '강동우' },
|
|
120
|
+
}),
|
|
121
|
+
).toEqual({
|
|
122
|
+
menuNo: MENU_NO.TEAM,
|
|
123
|
+
searchCnd: '1',
|
|
124
|
+
searchWrd: '강동우',
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('does not emit a searchId param (the native team page does not accept it)', () => {
|
|
129
|
+
const params = buildTeamListParams({
|
|
130
|
+
search: { field: 'mentor', value: '@me', me: true },
|
|
131
|
+
user: { userId: 'neo@example.com', userNm: '전수열' },
|
|
132
|
+
})
|
|
133
|
+
expect(params).not.toHaveProperty('searchId')
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('omits search params when "@me" is requested but the user identity is unavailable', () => {
|
|
137
|
+
expect(buildTeamListParams({ search: { field: 'mentor', value: '@me', me: true } })).toEqual({
|
|
138
|
+
menuNo: MENU_NO.TEAM,
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
})
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { MENU_NO } from '../../constants'
|
|
2
|
+
import type { UserIdentity } from '../../http'
|
|
3
|
+
|
|
4
|
+
const SEARCH_FIELD_MAP: Record<string, string> = {
|
|
5
|
+
all: '',
|
|
6
|
+
member: '1',
|
|
7
|
+
mentor: '2',
|
|
8
|
+
project: '3',
|
|
9
|
+
team: '4',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface TeamSearchQuery {
|
|
13
|
+
field: 'all' | 'member' | 'mentor' | 'project' | 'team'
|
|
14
|
+
value: string
|
|
15
|
+
me?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function parseTeamSearchQuery(raw: string): TeamSearchQuery {
|
|
19
|
+
const colonIndex = raw.indexOf(':')
|
|
20
|
+
if (colonIndex === -1) {
|
|
21
|
+
return { field: 'all', value: raw }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const field = raw.slice(0, colonIndex) as TeamSearchQuery['field']
|
|
25
|
+
if (!(field in SEARCH_FIELD_MAP)) {
|
|
26
|
+
return { field: 'all', value: raw }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const value = raw.slice(colonIndex + 1)
|
|
30
|
+
const me = (field === 'mentor' || field === 'member') && value === '@me'
|
|
31
|
+
return me ? { field, value, me } : { field, value }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function buildTeamListParams(options?: {
|
|
35
|
+
search?: TeamSearchQuery
|
|
36
|
+
user?: UserIdentity
|
|
37
|
+
}): Record<string, string> {
|
|
38
|
+
const params: Record<string, string> = { menuNo: MENU_NO.TEAM }
|
|
39
|
+
|
|
40
|
+
if (options?.search) {
|
|
41
|
+
if (options.search.me) {
|
|
42
|
+
if (options.user) {
|
|
43
|
+
params.searchCnd = SEARCH_FIELD_MAP[options.search.field]
|
|
44
|
+
params.searchWrd = options.user.userNm
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
params.searchCnd = SEARCH_FIELD_MAP[options.search.field]
|
|
48
|
+
params.searchWrd = options.search.value
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return params
|
|
53
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { afterEach, describe, expect, mock, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { TOZ_BASE_URL } from './constants'
|
|
4
|
+
import { TozClient } from './toz-client'
|
|
5
|
+
|
|
6
|
+
const originalFetch = globalThis.fetch
|
|
7
|
+
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
globalThis.fetch = originalFetch
|
|
10
|
+
mock.restore()
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
function jsonResponse(body: unknown, cookies: string[] = []): Response {
|
|
14
|
+
const response = new Response(JSON.stringify(body), {
|
|
15
|
+
status: 200,
|
|
16
|
+
headers: { 'Content-Type': 'application/json' },
|
|
17
|
+
})
|
|
18
|
+
Object.defineProperty(response.headers, 'getSetCookie', {
|
|
19
|
+
value: () => cookies,
|
|
20
|
+
configurable: true,
|
|
21
|
+
})
|
|
22
|
+
return response
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function textResponse(body: string, cookies: string[] = []): Response {
|
|
26
|
+
const response = new Response(body, { status: 200, headers: { 'Content-Type': 'text/html' } })
|
|
27
|
+
Object.defineProperty(response.headers, 'getSetCookie', {
|
|
28
|
+
value: () => cookies,
|
|
29
|
+
configurable: true,
|
|
30
|
+
})
|
|
31
|
+
return response
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe('TozClient.available', () => {
|
|
35
|
+
test('builds correct AJAX payload and parses booth response', async () => {
|
|
36
|
+
const calls: { url: string; body: string }[] = []
|
|
37
|
+
globalThis.fetch = mock(async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
38
|
+
const url = String(input)
|
|
39
|
+
const body = String(init?.body ?? '')
|
|
40
|
+
calls.push({ url, body })
|
|
41
|
+
|
|
42
|
+
if (url.endsWith('/index.htm')) return textResponse('ok', ['JSESSIONID=ABC'])
|
|
43
|
+
if (url.endsWith('/ajaxGetEnableBoothes.htm')) {
|
|
44
|
+
return jsonResponse([
|
|
45
|
+
{
|
|
46
|
+
resultMsg: 'SUCCESS',
|
|
47
|
+
id: 740,
|
|
48
|
+
name: '304 _ A',
|
|
49
|
+
branchName: '토즈타워점',
|
|
50
|
+
branchTel: '02-3454-0116',
|
|
51
|
+
minUseUserCount: 1,
|
|
52
|
+
enableMaxUserCount: 2,
|
|
53
|
+
boothGroupName: '2인부스 A타입',
|
|
54
|
+
boothGroupUrl: null,
|
|
55
|
+
boothMemoForUser: '15,000원',
|
|
56
|
+
isLargeBooth: false,
|
|
57
|
+
},
|
|
58
|
+
])
|
|
59
|
+
}
|
|
60
|
+
throw new Error(`Unexpected URL: ${url}`)
|
|
61
|
+
}) as typeof fetch
|
|
62
|
+
|
|
63
|
+
const client = new TozClient()
|
|
64
|
+
const booths = await client.available({
|
|
65
|
+
date: '2026-04-21',
|
|
66
|
+
startTime: '14:00',
|
|
67
|
+
durationMinutes: 120,
|
|
68
|
+
userCount: 2,
|
|
69
|
+
branchIds: [27, 145],
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
expect(booths).toHaveLength(1)
|
|
73
|
+
expect(booths[0]?.id).toBe(740)
|
|
74
|
+
expect(calls[1]?.url).toBe(`${TOZ_BASE_URL}/ajaxGetEnableBoothes.htm`)
|
|
75
|
+
expect(calls[1]?.body).toBe(
|
|
76
|
+
'basedate=2026-04-21&starttime=1400&durationTime=0200&userCount=2&branchIds=27%2C145%2C',
|
|
77
|
+
)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('throws on out-of-range duration', async () => {
|
|
81
|
+
const client = new TozClient()
|
|
82
|
+
await expect(
|
|
83
|
+
client.available({ date: '2026-04-21', startTime: '14:00', durationMinutes: 60, userCount: 2, branchIds: [27] }),
|
|
84
|
+
).rejects.toThrow(/2h and 3h/)
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
describe('TozClient.check', () => {
|
|
89
|
+
test('rejects empty time list', async () => {
|
|
90
|
+
const client = new TozClient()
|
|
91
|
+
await expect(
|
|
92
|
+
client.check({ date: '2026-04-21', startTimes: [], durationMinutes: 120, userCount: 2, branchIds: [27] }),
|
|
93
|
+
).rejects.toThrow(/at least one --time/)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
test('rejects more than 6 times', async () => {
|
|
97
|
+
const client = new TozClient()
|
|
98
|
+
await expect(
|
|
99
|
+
client.check({
|
|
100
|
+
date: '2026-04-21',
|
|
101
|
+
startTimes: ['10:00', '11:00', '12:00', '13:00', '14:00', '15:00', '16:00'],
|
|
102
|
+
durationMinutes: 120,
|
|
103
|
+
userCount: 2,
|
|
104
|
+
branchIds: [27],
|
|
105
|
+
}),
|
|
106
|
+
).rejects.toThrow(/Too many times/)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test('returns one result per time, captures errors per slot', async () => {
|
|
110
|
+
let call = 0
|
|
111
|
+
globalThis.fetch = mock(async (input: RequestInfo | URL) => {
|
|
112
|
+
const url = String(input)
|
|
113
|
+
if (url.endsWith('/index.htm')) return textResponse('ok', ['JSESSIONID=A'])
|
|
114
|
+
call += 1
|
|
115
|
+
if (call === 1) return jsonResponse([])
|
|
116
|
+
if (call === 2) return jsonResponse([{ resultMsg: '예약 가능한 부스가 없습니다.' }])
|
|
117
|
+
throw new Error(`Unexpected call ${call}`)
|
|
118
|
+
}) as typeof fetch
|
|
119
|
+
|
|
120
|
+
const client = new TozClient()
|
|
121
|
+
const results = await client.check({
|
|
122
|
+
date: '2026-04-21',
|
|
123
|
+
startTimes: ['10:00', '14:00'],
|
|
124
|
+
durationMinutes: 120,
|
|
125
|
+
userCount: 2,
|
|
126
|
+
branchIds: [27],
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
expect(results).toHaveLength(2)
|
|
130
|
+
expect(results[0]).toEqual({ startTime: '10:00', booths: [] })
|
|
131
|
+
expect(results[1]?.startTime).toBe('14:00')
|
|
132
|
+
expect(results[1]?.error).toContain('예약 가능한 부스가 없습니다.')
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('does not sleep after the last slot (single-time check is near-instant)', async () => {
|
|
136
|
+
globalThis.fetch = mock(async (input: RequestInfo | URL) => {
|
|
137
|
+
const url = String(input)
|
|
138
|
+
if (url.endsWith('/index.htm')) return textResponse('ok', ['JSESSIONID=A'])
|
|
139
|
+
return jsonResponse([])
|
|
140
|
+
}) as typeof fetch
|
|
141
|
+
|
|
142
|
+
const client = new TozClient()
|
|
143
|
+
const start = Date.now()
|
|
144
|
+
await client.check({
|
|
145
|
+
date: '2026-04-21',
|
|
146
|
+
startTimes: ['10:00'],
|
|
147
|
+
durationMinutes: 120,
|
|
148
|
+
userCount: 2,
|
|
149
|
+
branchIds: [27],
|
|
150
|
+
})
|
|
151
|
+
expect(Date.now() - start).toBeLessThan(400)
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
describe('TozClient identity fallback', () => {
|
|
156
|
+
test('uses constructor-provided name/phone when args omit them', async () => {
|
|
157
|
+
const calls: { url: string; body: string }[] = []
|
|
158
|
+
globalThis.fetch = mock(async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
159
|
+
const url = String(input)
|
|
160
|
+
calls.push({ url, body: String(init?.body ?? '') })
|
|
161
|
+
if (url.endsWith('/index.htm')) return textResponse('ok', ['JSESSIONID=A'])
|
|
162
|
+
if (url.endsWith('/mypage_login_.htm')) return textResponse('ok')
|
|
163
|
+
if (url.endsWith('/mypage.htm')) return textResponse('<html></html>')
|
|
164
|
+
throw new Error(`Unexpected ${url}`)
|
|
165
|
+
}) as typeof fetch
|
|
166
|
+
|
|
167
|
+
const client = new TozClient({ name: '홍길동', phone: '010-1234-5678' })
|
|
168
|
+
await client.myReservations()
|
|
169
|
+
|
|
170
|
+
const loginCall = calls.find((c) => c.url.endsWith('/mypage_login_.htm'))
|
|
171
|
+
expect(loginCall?.body).toContain('name=%ED%99%8D%EA%B8%B8%EB%8F%99')
|
|
172
|
+
expect(loginCall?.body).toContain('phone1=010')
|
|
173
|
+
expect(loginCall?.body).toContain('phone2=1234')
|
|
174
|
+
expect(loginCall?.body).toContain('phone3=5678')
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
test('throws a clear error when neither args nor identity are provided', async () => {
|
|
178
|
+
const client = new TozClient()
|
|
179
|
+
await expect(client.myReservations()).rejects.toThrow(/name is required/)
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
describe('TozClient.reserveBooth', () => {
|
|
184
|
+
test('rejects large booth', async () => {
|
|
185
|
+
globalThis.fetch = mock(async (input: RequestInfo | URL) => {
|
|
186
|
+
const url = String(input)
|
|
187
|
+
if (url.endsWith('/index.htm')) return textResponse('ok', ['JSESSIONID=A'])
|
|
188
|
+
if (url.endsWith('/ajaxReservationBooth.htm')) {
|
|
189
|
+
return jsonResponse({
|
|
190
|
+
result: 'SUCCESS',
|
|
191
|
+
resultMsg: 'res-1',
|
|
192
|
+
branchName: 'X',
|
|
193
|
+
branchTel: 'tel',
|
|
194
|
+
boothGroupName: '대형부스',
|
|
195
|
+
boothIsLarge: true,
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
throw new Error(url)
|
|
199
|
+
}) as typeof fetch
|
|
200
|
+
|
|
201
|
+
const client = new TozClient()
|
|
202
|
+
await expect(
|
|
203
|
+
client.reserveBooth({ date: '2026-04-21', startTime: '14:00', durationMinutes: 120, userCount: 8, boothId: 999 }),
|
|
204
|
+
).rejects.toThrow(/대형부스/)
|
|
205
|
+
})
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
describe('TozClient.confirm', () => {
|
|
209
|
+
test('throws on non-success message', async () => {
|
|
210
|
+
globalThis.fetch = mock(async () => jsonResponse({ resultMsg: '인증번호가 올바르지 않습니다.' })) as typeof fetch
|
|
211
|
+
|
|
212
|
+
const client = new TozClient({ sessionCookie: 'A' })
|
|
213
|
+
await expect(
|
|
214
|
+
client.confirm({
|
|
215
|
+
reservationId: 'r1',
|
|
216
|
+
date: '2026-04-21',
|
|
217
|
+
startTime: '14:00',
|
|
218
|
+
durationMinutes: 120,
|
|
219
|
+
name: '홍길동',
|
|
220
|
+
phone: '010-1234-5678',
|
|
221
|
+
email: 'me@gmail.com',
|
|
222
|
+
pinNum: '000000',
|
|
223
|
+
meetingId: 1234,
|
|
224
|
+
}),
|
|
225
|
+
).rejects.toThrow('인증번호가 올바르지 않습니다.')
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
test('requires meetingId or newMeetingName', async () => {
|
|
229
|
+
const client = new TozClient({ sessionCookie: 'A' })
|
|
230
|
+
await expect(
|
|
231
|
+
client.confirm({
|
|
232
|
+
reservationId: 'r1',
|
|
233
|
+
date: '2026-04-21',
|
|
234
|
+
startTime: '14:00',
|
|
235
|
+
durationMinutes: 120,
|
|
236
|
+
name: '홍길동',
|
|
237
|
+
phone: '010-1234-5678',
|
|
238
|
+
email: 'me@gmail.com',
|
|
239
|
+
pinNum: '123456',
|
|
240
|
+
}),
|
|
241
|
+
).rejects.toThrow(/meetingId/)
|
|
242
|
+
})
|
|
243
|
+
})
|