opensoma 0.3.0 → 0.5.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.
Files changed (89) hide show
  1. package/dist/package.json +1 -1
  2. package/dist/src/client.d.ts +3 -3
  3. package/dist/src/client.d.ts.map +1 -1
  4. package/dist/src/client.js +37 -5
  5. package/dist/src/client.js.map +1 -1
  6. package/dist/src/commands/auth.d.ts +1 -1
  7. package/dist/src/commands/auth.d.ts.map +1 -1
  8. package/dist/src/commands/auth.js +94 -52
  9. package/dist/src/commands/auth.js.map +1 -1
  10. package/dist/src/commands/mentoring.d.ts.map +1 -1
  11. package/dist/src/commands/mentoring.js +26 -20
  12. package/dist/src/commands/mentoring.js.map +1 -1
  13. package/dist/src/commands/room.d.ts.map +1 -1
  14. package/dist/src/commands/room.js +25 -2
  15. package/dist/src/commands/room.js.map +1 -1
  16. package/dist/src/constants.d.ts +52 -9
  17. package/dist/src/constants.d.ts.map +1 -1
  18. package/dist/src/constants.js +65 -9
  19. package/dist/src/constants.js.map +1 -1
  20. package/dist/src/formatters.d.ts.map +1 -1
  21. package/dist/src/formatters.js +79 -39
  22. package/dist/src/formatters.js.map +1 -1
  23. package/dist/src/http.d.ts +3 -0
  24. package/dist/src/http.d.ts.map +1 -1
  25. package/dist/src/http.js +42 -1
  26. package/dist/src/http.js.map +1 -1
  27. package/dist/src/index.d.ts +3 -0
  28. package/dist/src/index.d.ts.map +1 -1
  29. package/dist/src/index.js +2 -0
  30. package/dist/src/index.js.map +1 -1
  31. package/dist/src/shared/utils/html.d.ts +3 -0
  32. package/dist/src/shared/utils/html.d.ts.map +1 -0
  33. package/dist/src/shared/utils/html.js +12 -0
  34. package/dist/src/shared/utils/html.js.map +1 -0
  35. package/dist/src/shared/utils/swmaestro.d.ts +2 -0
  36. package/dist/src/shared/utils/swmaestro.d.ts.map +1 -1
  37. package/dist/src/shared/utils/swmaestro.js +28 -5
  38. package/dist/src/shared/utils/swmaestro.js.map +1 -1
  39. package/dist/src/shared/utils/toz.d.ts +23 -0
  40. package/dist/src/shared/utils/toz.d.ts.map +1 -0
  41. package/dist/src/shared/utils/toz.js +72 -0
  42. package/dist/src/shared/utils/toz.js.map +1 -0
  43. package/dist/src/token-extractor.d.ts +9 -1
  44. package/dist/src/token-extractor.d.ts.map +1 -1
  45. package/dist/src/token-extractor.js +54 -10
  46. package/dist/src/token-extractor.js.map +1 -1
  47. package/dist/src/toz-formatters.d.ts +9 -0
  48. package/dist/src/toz-formatters.d.ts.map +1 -0
  49. package/dist/src/toz-formatters.js +151 -0
  50. package/dist/src/toz-formatters.js.map +1 -0
  51. package/dist/src/toz-http.d.ts +27 -0
  52. package/dist/src/toz-http.d.ts.map +1 -0
  53. package/dist/src/toz-http.js +154 -0
  54. package/dist/src/toz-http.js.map +1 -0
  55. package/dist/src/types.d.ts +88 -0
  56. package/dist/src/types.d.ts.map +1 -1
  57. package/dist/src/types.js +65 -1
  58. package/dist/src/types.js.map +1 -1
  59. package/package.json +1 -1
  60. package/src/__fixtures__/toz/toz_all_branches.json +211 -0
  61. package/src/__fixtures__/toz/toz_booking.html +2190 -0
  62. package/src/__fixtures__/toz/toz_boothes.json +59 -0
  63. package/src/__fixtures__/toz/toz_duration.json +25 -0
  64. package/src/__fixtures__/toz/toz_mypage_response.html +388 -0
  65. package/src/__fixtures__/toz/toz_page.html +211 -0
  66. package/src/client.test.ts +63 -16
  67. package/src/client.ts +43 -6
  68. package/src/commands/auth.ts +107 -50
  69. package/src/commands/mentoring.ts +34 -26
  70. package/src/commands/room.ts +31 -3
  71. package/src/constants.ts +74 -9
  72. package/src/formatters.test.ts +6 -2
  73. package/src/formatters.ts +92 -45
  74. package/src/http.test.ts +215 -0
  75. package/src/http.ts +45 -1
  76. package/src/index.ts +3 -0
  77. package/src/shared/utils/html.ts +12 -0
  78. package/src/shared/utils/swmaestro.test.ts +44 -0
  79. package/src/shared/utils/swmaestro.ts +30 -5
  80. package/src/shared/utils/toz.test.ts +133 -0
  81. package/src/shared/utils/toz.ts +100 -0
  82. package/src/token-extractor.test.ts +30 -5
  83. package/src/token-extractor.ts +65 -13
  84. package/src/toz-formatters.test.ts +197 -0
  85. package/src/toz-formatters.ts +211 -0
  86. package/src/toz-http.test.ts +157 -0
  87. package/src/toz-http.ts +188 -0
  88. package/src/types.test.ts +4 -1
  89. package/src/types.ts +81 -1
@@ -0,0 +1,211 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
5
+ <title>토즈</title>
6
+
7
+ <script type="text/javascript" src="/js/jquery-1.4.4.min.js"></script>
8
+ <script type="text/javascript" src="/js/jquery-ui-1.8.9.custom.min.js"></script>
9
+ <script type="text/javascript" src="/js/jquery.ui.datepicker-ko.js"></script>
10
+ <link type="text/css" href="/css/blitzer/jquery-ui-1.8.13.custom.css" rel="stylesheet" />
11
+ <link type="text/css" href="/css/partnerReservation.css" rel="stylesheet" />
12
+ <style>
13
+ .header {
14
+ background: #2c2c2c;
15
+ height: 80px;
16
+ border-bottom: 5px solid #ccc;
17
+ }
18
+
19
+ .header .logo {
20
+ position: relative;
21
+ width: 950px;
22
+ margin: 0 auto;
23
+ padding: 40px 0 0 0;
24
+ height: 40px;
25
+ }
26
+ .header .partner_logo {
27
+ color: white;
28
+ font-weight: bold;
29
+ font-size: 17px;
30
+ letter-spacing: -1px;
31
+ position: absolute;
32
+ top: 0;
33
+ right: 0;
34
+ padding: 50px 0 0 0;
35
+ height: 40px;
36
+ }
37
+ ul.btn-list {
38
+ position: relative;
39
+ list-style: none;
40
+ margin: 0;
41
+ padding: 0;
42
+ height: 100%;
43
+ }
44
+
45
+ ul.btn-list li {
46
+ position: relative;
47
+ margin: 0;
48
+ text-align: left;
49
+ width: 100%;
50
+ }
51
+
52
+ ul.btn-list li.btn-list-1 {
53
+ padding: 42px 0px;
54
+ }
55
+ ul.btn-list li.btn-list-3 {
56
+ padding: 42px 0px;
57
+ }
58
+ ul.btn-list li.btn-list-4 {
59
+ padding: 49px 0px;
60
+ }
61
+ </style>
62
+ </head>
63
+ <body>
64
+ <!-- top -->
65
+ <div class="header" style="padding-bottom: 6px">
66
+ <div class="logo" style="padding-bottom: 6px">
67
+ <div>
68
+ <a href="/partner/reservation/fkii3/swmaestro/index.htm?key=&projectSeq=&addedInfo=&tozApplyType="
69
+ ><img src="/images/partner/toz_logo.png"
70
+ /></a>
71
+ </div>
72
+ <div class="partner_logo">
73
+ 기업전용 문의접수: <a href="mailto: b2bsb@toz.co.kr" style="color: white">b2bsb@toz.co.kr</a>
74
+ </div>
75
+ </div>
76
+ </div>
77
+ <div style="display: flex; flex-direction: row; justify-content: center; margin: -5px 0 0 0">
78
+ <div
79
+ class="layoutBody"
80
+ style="
81
+ position: relative;
82
+ display: flex;
83
+ flex-direction: column;
84
+ justify-content: center;
85
+ border-top: 5px solid #b00e33;
86
+ "
87
+ >
88
+ <div style="display: flex; flex-direction: row; width: 950px; border-bottom: 2px solid #d03f5f">
89
+ <div style="position: relative; width: 700px; z-index: 1">
90
+ <div style="padding-left: 30px; width: 380px; word-break: keep-all">
91
+ <p style="font-weight: bold; color: #890624; font-size: 18px; margin: 20px 0 10px 0">
92
+ (필독) 외부 회의실 예약 및 이용시 주의사항
93
+ </p>
94
+ <p></p>
95
+ <p style="font-size: 13px; color: #939393; font-weight: bold; line-height: 25px">
96
+ · 최소 2시간 부터 사용 가능하며 최대 3시간까지 사용합니다.<br />
97
+ · 예약 후 멘토링/특강 일정 변경시 반드시 예약 취소합니다.<br />
98
+ · 본인의 예약 미취소로 인해 발생한 비용은 개인에게<br />
99
+ <span style="display: inline-block; padding-left: 10px">직접 청구 될 수 있습니다.</span><br />
100
+ · 이용자(연수생/엑스퍼트/멘토) 경고에 해당하는 경우<br />
101
+ <span style="display: inline-block; padding-left: 14px">(1) 예약 후 예약 취소 없이 일방적인 노쇼</span
102
+ ><br />
103
+ <span style="display: inline-block; padding-left: 14px"
104
+ >(2) 멘토-연수생, 엑스퍼트-연수생과의 활동이 아닌</span
105
+ ><br />
106
+ <span style="display: inline-block; padding-left: 36px">목적 외 활동을 한 경우</span><br />
107
+ <span style="display: inline-block; padding-left: 14px">(3) 기타 부적합한 용도로 사용한 경우</span>
108
+ </p>
109
+ </div>
110
+
111
+ <div style="position: absolute; bottom: 10px; left: 40px">
112
+ <div class="companyName" style="padding: 5px 0">
113
+ <span style="font-weight: bold; color: #2c2c2c; border-bottom: 1px solid #939393; padding-bottom: 2px"
114
+ >AI‧SW마에스트로</span
115
+ >
116
+ </div>
117
+ <div
118
+ class="text"
119
+ style="padding: 5px 0; font-size: 14px; font-weight: bold; color: #939393; letter-spacing: -1px"
120
+ >
121
+ 제휴내용은
122
+ </div>
123
+ <div style="padding: 5px 0">
124
+ <span class="desc" style="color: #890624; border-bottom: 1px solid #939393; padding-bottom: 2px"
125
+ >20% 할인</span
126
+ ><span class="text"> 입니다.</span>
127
+ </div>
128
+ </div>
129
+ </div>
130
+ <div style="position: relative; width: 250px; height: 388px">
131
+ <img src="/images/partner/deco_toz_main_1.png" style="position: absolute; bottom: 0px; right: 250px" />
132
+ <ul class="btn-list">
133
+ <li class="btn-list-1" style="background: #890624">
134
+ <a href="/partner/reservation/fkii3/swmaestro/booking.htm?key=&projectSeq=&addedInfo=&tozApplyType="
135
+ ><img alt="예약하기" src="/images/partner/btn_toz_main_1.png" style="margin-left: 30px"
136
+ /></a>
137
+ </li>
138
+
139
+ <li class="btn-list-3" style="background: #c42649">
140
+ <a href="/partner/reservation/fkii3/swmaestro/startmypage.htm?key=&projectSeq=&addedInfo=&tozApplyType="
141
+ ><img alt="예약확인하기" src="/images/partner/btn_toz_main_3.png" style="margin-left: 30px"
142
+ /></a>
143
+ </li>
144
+ <li class="btn-list-4" style="background: #d03f5f">
145
+ <a href="https://work.toz.co.kr/branchSearch?page=1&onesBranchType=TMC" target="_blank"
146
+ ><img alt="토즈모임센터 지점안내" src="/images/partner/btn_toz_main_4.png" style="margin-left: 30px"
147
+ /></a>
148
+ </li>
149
+ </ul>
150
+ </div>
151
+ </div>
152
+
153
+ <div
154
+ style="
155
+ display: flex;
156
+ flex-direction: row;
157
+ justify-content: center;
158
+ position: relative;
159
+ width: 950px;
160
+ margin: 0 auto;
161
+ text-align: center;
162
+ border-bottom: 2px solid #d03f5f;
163
+ "
164
+ >
165
+ <img src="/images/partner/btn_link_map.png" usemap="#map_link" style="width: 808px; height: 168px" />
166
+ <map name="map_link">
167
+ <area
168
+ shape="rect"
169
+ coords="10,30,210,160"
170
+ href="https://moim.toz.co.kr"
171
+ title="토즈 서비스 그룹"
172
+ target="_blank"
173
+ />
174
+ <area
175
+ shape="rect"
176
+ coords="280,30,510,160"
177
+ href="https://moim.toz.co.kr/customerCenter/posts/all"
178
+ title="토즈 고객센터"
179
+ target="_blank"
180
+ />
181
+ <area
182
+ shape="rect"
183
+ coords="570,25,810,160"
184
+ href="https://moim.toz.co.kr/customerCenter/posts/event"
185
+ title="토즈 이벤트"
186
+ target="_blank"
187
+ />
188
+ </map>
189
+ </div>
190
+ </div>
191
+ </div>
192
+
193
+ <!-- google analytics for (http)partner.toz.co.kr -->
194
+ <script type="text/javascript">
195
+ var _gaq = _gaq || []
196
+ _gaq.push(['_setAccount', 'UA-23075111-3'])
197
+ _gaq.push(['_trackPageview'])
198
+
199
+ ;(function () {
200
+ var ga = document.createElement('script')
201
+ ga.type = 'text/javascript'
202
+ ga.async = true
203
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'
204
+ var s = document.getElementsByTagName('script')[0]
205
+ s.parentNode.insertBefore(ga, s)
206
+ })()
207
+ </script>
208
+ <!-- // google analytics for (http)partner.toz.co.kr -->
209
+ <!-- // bottom -->
210
+ </body>
211
+ </html>
@@ -72,14 +72,19 @@ describe('SomaClient', () => {
72
72
  })
73
73
 
74
74
  test('mutating operations post expected payloads', async () => {
75
+ const mentoringDetailHtml =
76
+ '<div class="group"><strong class="t">모집 명</strong><div class="c">[자유 멘토링] 기존 멘토링</div></div><div class="group"><strong class="t">접수 기간</strong><div class="c">2026.03.01 ~ 2026.03.15</div></div><div class="group"><strong class="t">강의날짜</strong><div class="c"><span>2026.03.20 10:00시 ~ 12:00시</span></div></div><div class="group"><strong class="t">장소</strong><div class="c">온라인(Webex)</div></div><div class="group"><strong class="t">모집인원</strong><div class="c">5명</div></div><div class="group"><strong class="t">작성자</strong><div class="c">전수열</div></div><div class="group"><strong class="t">등록일</strong><div class="c">2026.03.01</div></div><div class="cont"><p>기존 내용</p></div>'
75
77
  const client = new SomaClient()
76
- const calls: Array<{ path: string; data: Record<string, string> }> = []
78
+ const calls: Array<{ method: string; path: string; data: Record<string, string> }> = []
79
+ const captor = (method: string) => async (path: string, data: Record<string, string>) => {
80
+ calls.push({ method, path, data })
81
+ return ''
82
+ }
77
83
  Reflect.set(client, 'http', {
78
84
  checkLogin: async () => ({ userId: 'user@example.com', userNm: 'Test' }),
79
- post: async (path: string, data: Record<string, string>) => {
80
- calls.push({ path, data })
81
- return ''
82
- },
85
+ get: async () => mentoringDetailHtml,
86
+ post: captor('post'),
87
+ postForm: captor('postForm'),
83
88
  })
84
89
 
85
90
  await client.mentoring.create({
@@ -110,14 +115,14 @@ describe('SomaClient', () => {
110
115
  })
111
116
  await client.event.apply(11)
112
117
 
113
- expect(calls.map((call) => call.path)).toEqual([
114
- '/mypage/mentoLec/insert.do',
115
- '/mypage/mentoLec/update.do',
116
- '/mypage/mentoLec/delete.do',
117
- '/application/application/application.do',
118
- '/mypage/userAnswer/cancel.do',
119
- '/mypage/itemRent/insert.do',
120
- '/application/application/application.do',
118
+ expect(calls.map((call) => `${call.method}:${call.path}`)).toEqual([
119
+ 'postForm:/mypage/mentoLec/insert.do',
120
+ 'postForm:/mypage/mentoLec/update.do',
121
+ 'post:/mypage/mentoLec/delete.do',
122
+ 'post:/application/application/application.do',
123
+ 'post:/mypage/userAnswer/cancel.do',
124
+ 'post:/mypage/itemRent/insert.do',
125
+ 'post:/application/application/application.do',
121
126
  ])
122
127
  expect(calls[0]?.data).toMatchObject({
123
128
  menuNo: MENU_NO.MENTORING,
@@ -161,6 +166,38 @@ describe('SomaClient', () => {
161
166
  })
162
167
  })
163
168
 
169
+ test('mentoring.update merges partial params with existing data', async () => {
170
+ const mentoringDetailHtml =
171
+ '<div class="group"><strong class="t">모집 명</strong><div class="c">[멘토 특강] 웹 성능 특강</div></div><div class="group"><strong class="t">접수 기간</strong><div class="c">2026.04.01 ~ 2026.04.10</div></div><div class="group"><strong class="t">강의날짜</strong><div class="c"><span>2026.04.11 14:00시 ~ 15:30시</span></div></div><div class="group"><strong class="t">장소</strong><div class="c">온라인(Webex)</div></div><div class="group"><strong class="t">모집인원</strong><div class="c">20명</div></div><div class="group"><strong class="t">작성자</strong><div class="c">전수열</div></div><div class="group"><strong class="t">등록일</strong><div class="c">2026.04.01</div></div><div class="cont"><p>세션 본문</p></div>'
172
+ const client = new SomaClient()
173
+ const postFormCalls: Array<{ path: string; data: Record<string, string> }> = []
174
+ Reflect.set(client, 'http', {
175
+ checkLogin: async () => ({ userId: 'user@example.com', userNm: 'Test' }),
176
+ get: async () => mentoringDetailHtml,
177
+ postForm: async (path: string, data: Record<string, string>) => {
178
+ postFormCalls.push({ path, data })
179
+ return ''
180
+ },
181
+ })
182
+
183
+ await client.mentoring.update(9572, { title: '변경된 제목' })
184
+
185
+ expect(postFormCalls).toHaveLength(1)
186
+ expect(postFormCalls[0]?.data).toMatchObject({
187
+ qustnrSj: '변경된 제목',
188
+ qustnrSn: '9572',
189
+ reportCd: 'MRC020',
190
+ eventDt: '2026-04-11',
191
+ eventStime: '14:00',
192
+ eventEtime: '15:30',
193
+ place: '온라인(Webex)',
194
+ applyCnt: '20',
195
+ bgnde: '2026-04-01',
196
+ endde: '2026-04-10',
197
+ qestnarCn: '<p>세션 본문</p>',
198
+ })
199
+ })
200
+
164
201
  test('room, dashboard, notice, team, member, event, and history routes use expected endpoints', async () => {
165
202
  const client = new SomaClient()
166
203
  const calls: Array<{ method: string; path: string; data: Record<string, string> | undefined }> = []
@@ -197,13 +234,13 @@ describe('SomaClient', () => {
197
234
  post: async (path: string, data: Record<string, string>) => {
198
235
  calls.push({ method: 'post', path, data })
199
236
  if (path === '/mypage/officeMng/rentTime.do') {
200
- return '<div class="time-grid"><span>09:00</span></div>'
237
+ return '<span class="ck-st2 disabled" data-hour="09" data-minute="00"><input type="checkbox" disabled="disabled"><label title="아침 회의&lt;br&gt;예약자 : 김오픈">오전 09:00</label></span>'
201
238
  }
202
239
  return '<ul class="bbs-reserve"><li class="item"><a href="javascript:void(0);" onclick="location.href=\'/sw/mypage/officeMng/view.do?itemId=17\';"><div class="cont"><h4 class="tit">스페이스 A1</h4><ul class="txt bul-dot grey"><li>이용기간 : 2026-04-01 ~ 2026-12-31</li><li><p>설명 : 4인</p></li><li class="time-list"><div class="time-grid"><span>09:00</span></div></li></ul></div></a></li></ul>'
203
240
  },
204
241
  })
205
242
 
206
- const roomList = await client.room.list({ date: '2026-04-01', room: 'A1' })
243
+ const roomList = await client.room.list({ date: '2026-04-01', room: 'A1', includeReservations: true })
207
244
  const roomSlots = await client.room.available(17, '2026-04-01')
208
245
  const dashboard = await client.dashboard.get()
209
246
  const noticeList = await client.notice.list({ page: 2 })
@@ -215,7 +252,12 @@ describe('SomaClient', () => {
215
252
  const history = await client.mentoring.history({ page: 4 })
216
253
 
217
254
  expect(roomList[0]?.itemId).toBe(17)
218
- expect(roomSlots).toEqual([{ time: '09:00', available: true }])
255
+ expect(roomList[0]?.timeSlots).toEqual([
256
+ { time: '09:00', available: false, reservation: { title: '아침 회의', bookedBy: '김오픈' } },
257
+ ])
258
+ expect(roomSlots).toEqual([
259
+ { time: '09:00', available: false, reservation: { title: '아침 회의', bookedBy: '김오픈' } },
260
+ ])
219
261
  expect(dashboard.name).toBe('전수열')
220
262
  expect(dashboard.mentoringSessions).toEqual([
221
263
  {
@@ -249,6 +291,11 @@ describe('SomaClient', () => {
249
291
 
250
292
  const dashboardCallIndex = calls.findIndex((c) => c.path === '/mypage/myMain/dashboard.do')
251
293
  expect(dashboardCallIndex).toBeGreaterThanOrEqual(0)
294
+ expect(calls).toContainEqual({
295
+ method: 'post',
296
+ path: '/mypage/officeMng/rentTime.do',
297
+ data: { viewType: 'CONTBODY', itemId: '17', rentDt: '2026-04-01' },
298
+ })
252
299
  const mentoringListCall = calls.find((c) => c.path === '/mypage/mentoLec/list.do')
253
300
  expect(mentoringListCall?.data).toEqual({
254
301
  menuNo: MENU_NO.MENTORING,
package/src/client.ts CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  buildUpdateMentoringPayload,
17
17
  parseEventDetail,
18
18
  resolveRoomId,
19
+ toMentoringType,
19
20
  toRegionCode,
20
21
  toReportTypeCd,
21
22
  } from './shared/utils/swmaestro'
@@ -27,6 +28,7 @@ import type {
27
28
  MemberInfo,
28
29
  MentoringDetail,
29
30
  MentoringListItem,
31
+ MentoringUpdateOptions,
30
32
  NoticeDetail,
31
33
  NoticeListItem,
32
34
  Pagination,
@@ -71,7 +73,7 @@ export class SomaClient {
71
73
  regEnd?: string
72
74
  content?: string
73
75
  }): Promise<void>
74
- update(id: number, params: Parameters<typeof buildMentoringPayload>[0]): Promise<void>
76
+ update(id: number, params: MentoringUpdateOptions): Promise<void>
75
77
  delete(id: number): Promise<void>
76
78
  apply(id: number): Promise<void>
77
79
  cancel(params: { applySn: number; qustnrSn: number }): Promise<void>
@@ -79,7 +81,7 @@ export class SomaClient {
79
81
  }
80
82
 
81
83
  readonly room: {
82
- list(options?: { date?: string; room?: string }): Promise<RoomCard[]>
84
+ list(options?: { date?: string; room?: string; includeReservations?: boolean }): Promise<RoomCard[]>
83
85
  available(roomId: number, date: string): Promise<RoomCard['timeSlots']>
84
86
  reserve(params: {
85
87
  roomId: number
@@ -176,14 +178,27 @@ export class SomaClient {
176
178
  },
177
179
  create: async (params) => {
178
180
  await this.requireAuth()
179
- const html = await this.http.post('/mypage/mentoLec/insert.do', buildMentoringPayload(params))
181
+ const html = await this.http.postForm('/mypage/mentoLec/insert.do', buildMentoringPayload(params))
180
182
  if (this.containsErrorIndicator(html)) {
181
183
  throw new Error(this.extractErrorMessage(html) || '멘토링 등록에 실패했습니다.')
182
184
  }
183
185
  },
184
186
  update: async (id, params) => {
185
187
  await this.requireAuth()
186
- const html = await this.http.post('/mypage/mentoLec/update.do', buildUpdateMentoringPayload(id, params))
188
+ const existing = await this.mentoring.get(id)
189
+ const merged = buildUpdateMentoringPayload(id, {
190
+ title: params.title ?? existing.title,
191
+ type: params.type ?? toMentoringType(existing.type),
192
+ date: params.date ?? existing.sessionDate,
193
+ startTime: params.startTime ?? existing.sessionTime.start,
194
+ endTime: params.endTime ?? existing.sessionTime.end,
195
+ venue: params.venue ?? existing.venue,
196
+ maxAttendees: params.maxAttendees ?? existing.attendees.max,
197
+ regStart: params.regStart ?? existing.registrationPeriod.start,
198
+ regEnd: params.regEnd ?? existing.registrationPeriod.end,
199
+ content: params.content ?? existing.content,
200
+ })
201
+ const html = await this.http.postForm('/mypage/mentoLec/update.do', merged)
187
202
  if (this.containsErrorIndicator(html)) {
188
203
  throw new Error(this.extractErrorMessage(html) || '멘토링 수정에 실패했습니다.')
189
204
  }
@@ -216,13 +231,35 @@ export class SomaClient {
216
231
  this.room = {
217
232
  list: async (options) => {
218
233
  await this.requireAuth()
219
- return formatters.parseRoomList(
234
+ const date = options?.date ?? new Date().toISOString().slice(0, 10)
235
+ const rooms = formatters.parseRoomList(
220
236
  await this.http.post('/mypage/officeMng/list.do', {
221
237
  menuNo: MENU_NO.ROOM,
222
- sdate: options?.date ?? new Date().toISOString().slice(0, 10),
238
+ sdate: date,
223
239
  searchItemId: options?.room ? String(resolveRoomId(options.room)) : '',
224
240
  }),
225
241
  )
242
+
243
+ if (!options?.includeReservations) return rooms
244
+
245
+ return Promise.all(
246
+ rooms.map(async (room) => {
247
+ try {
248
+ const html = await this.http.post('/mypage/officeMng/rentTime.do', {
249
+ viewType: 'CONTBODY',
250
+ itemId: String(room.itemId),
251
+ rentDt: date,
252
+ })
253
+
254
+ return {
255
+ ...room,
256
+ timeSlots: formatters.parseRoomSlots(html),
257
+ }
258
+ } catch {
259
+ return room
260
+ }
261
+ }),
262
+ )
226
263
  },
227
264
  available: async (roomId, date) => {
228
265
  await this.requireAuth()