opensoma 0.5.0 → 0.6.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 +3 -2
- package/dist/src/cli.js.map +1 -1
- package/dist/src/client.d.ts +36 -7
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/client.js +231 -63
- 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 +2 -1
- package/dist/src/commands/index.d.ts.map +1 -1
- package/dist/src/commands/index.js +2 -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/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 +9 -0
- package/dist/src/credential-manager.d.ts.map +1 -1
- package/dist/src/credential-manager.js +24 -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 +4 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -1
- package/dist/src/index.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 -43
- 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/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 +4 -2
- package/src/client.test.ts +801 -140
- package/src/client.ts +293 -79
- package/src/commands/agent-browser.ts +33 -0
- package/src/commands/auth.test.ts +83 -32
- 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 +79 -32
- package/src/commands/helpers.ts +3 -3
- package/src/commands/index.ts +2 -1
- package/src/commands/mentoring.ts +60 -29
- package/src/commands/notice.ts +2 -1
- package/src/commands/report.test.ts +7 -7
- 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/constants.ts +20 -8
- package/src/credential-manager.test.ts +49 -5
- package/src/credential-manager.ts +27 -0
- package/src/formatters.test.ts +548 -53
- package/src/formatters.ts +309 -55
- package/src/http.test.ts +108 -39
- package/src/http.ts +41 -2
- package/src/index.ts +10 -1
- package/src/shared/utils/mentoring-params.test.ts +16 -16
- package/src/shared/utils/swmaestro.test.ts +326 -11
- package/src/shared/utils/swmaestro.ts +150 -52
- 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/shared/utils/toz.test.ts +12 -7
- package/src/token-extractor.test.ts +12 -12
- package/src/toz-http.test.ts +11 -11
- package/src/types.test.ts +235 -206
- 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
|
+
}
|
|
@@ -52,9 +52,12 @@ describe('formatStartTime / buildStartTimeParam', () => {
|
|
|
52
52
|
expect(buildStartTimeParam('9:05')).toBe('0905')
|
|
53
53
|
})
|
|
54
54
|
|
|
55
|
-
it('rejects
|
|
55
|
+
it('rejects time without colon separator', () => {
|
|
56
56
|
expect(() => formatStartTime('1000')).toThrow()
|
|
57
|
-
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('accepts out-of-range hours because the format matches', () => {
|
|
60
|
+
expect(formatStartTime('25:00')).toEqual({ hour: '25', minute: '00' })
|
|
58
61
|
})
|
|
59
62
|
})
|
|
60
63
|
|
|
@@ -120,13 +123,15 @@ describe('parseEmail', () => {
|
|
|
120
123
|
})
|
|
121
124
|
|
|
122
125
|
describe('assertDurationInRange', () => {
|
|
123
|
-
it('accepts
|
|
124
|
-
expect(
|
|
125
|
-
expect(
|
|
126
|
-
expect(
|
|
126
|
+
it('accepts boundary and interior values', () => {
|
|
127
|
+
expect(assertDurationInRange(120)).toBeUndefined()
|
|
128
|
+
expect(assertDurationInRange(150)).toBeUndefined()
|
|
129
|
+
expect(assertDurationInRange(180)).toBeUndefined()
|
|
127
130
|
})
|
|
128
131
|
|
|
129
|
-
it('rejects
|
|
132
|
+
it('rejects values just outside the boundary', () => {
|
|
133
|
+
expect(() => assertDurationInRange(119)).toThrow()
|
|
134
|
+
expect(() => assertDurationInRange(181)).toThrow()
|
|
130
135
|
expect(() => assertDurationInRange(60)).toThrow()
|
|
131
136
|
expect(() => assertDurationInRange(210)).toThrow()
|
|
132
137
|
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Database } from 'bun:sqlite'
|
|
2
|
-
import { afterEach, describe, expect,
|
|
2
|
+
import { afterEach, describe, expect, it } from 'bun:test'
|
|
3
3
|
import { execSync } from 'node:child_process'
|
|
4
4
|
import { createCipheriv, pbkdf2Sync } from 'node:crypto'
|
|
5
5
|
import { mkdirSync, rmSync, writeFileSync } from 'node:fs'
|
|
@@ -22,7 +22,7 @@ afterEach(() => {
|
|
|
22
22
|
})
|
|
23
23
|
|
|
24
24
|
describe('TokenExtractor', () => {
|
|
25
|
-
|
|
25
|
+
it('finds cookie databases for every supported browser on macOS', async () => {
|
|
26
26
|
const home = await makeTempDir()
|
|
27
27
|
|
|
28
28
|
for (const browser of BROWSERS) {
|
|
@@ -38,7 +38,7 @@ describe('TokenExtractor', () => {
|
|
|
38
38
|
}
|
|
39
39
|
})
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
it('finds cookie databases for every supported browser on Linux', async () => {
|
|
42
42
|
const home = await makeTempDir()
|
|
43
43
|
|
|
44
44
|
for (const browser of BROWSERS) {
|
|
@@ -51,12 +51,12 @@ describe('TokenExtractor', () => {
|
|
|
51
51
|
expect(paths).toHaveLength(BROWSERS.length)
|
|
52
52
|
})
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
it('returns null when no cookie databases exist', async () => {
|
|
55
55
|
const extractor = new TokenExtractor('linux', await makeTempDir())
|
|
56
56
|
expect(await extractor.extract()).toBeNull()
|
|
57
57
|
})
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
it('extracts a plaintext cookie value', async () => {
|
|
60
60
|
const home = await makeTempDir()
|
|
61
61
|
const dbPath = join(home, '.config', 'google-chrome', 'Default', 'Cookies')
|
|
62
62
|
createCookieDbWithPlaintext(dbPath, 'my-session-id')
|
|
@@ -65,7 +65,7 @@ describe('TokenExtractor', () => {
|
|
|
65
65
|
expect(await extractor.extract()).toEqual({ sessionCookie: 'my-session-id' })
|
|
66
66
|
})
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
it('finds cookie databases across numbered browser profiles', async () => {
|
|
69
69
|
const home = await makeTempDir()
|
|
70
70
|
createCookieFile(join(home, '.config', 'google-chrome', 'Default', 'Cookies'))
|
|
71
71
|
createCookieFile(join(home, '.config', 'google-chrome', 'Profile 1', 'Cookies'))
|
|
@@ -80,7 +80,7 @@ describe('TokenExtractor', () => {
|
|
|
80
80
|
])
|
|
81
81
|
})
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
it('keeps the newest unique cookie across profiles when collecting candidates', async () => {
|
|
84
84
|
const home = await makeTempDir()
|
|
85
85
|
createCookieDbWithPlaintext(join(home, '.config', 'google-chrome', 'Default', 'Cookies'), 'stale-session', 10)
|
|
86
86
|
createCookieDbWithPlaintext(join(home, '.config', 'google-chrome', 'Profile 1', 'Cookies'), 'valid-session', 20)
|
|
@@ -104,7 +104,7 @@ describe('TokenExtractor', () => {
|
|
|
104
104
|
])
|
|
105
105
|
})
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
it('extracts a plaintext cookie from the opensoma.dev host', async () => {
|
|
108
108
|
const home = await makeTempDir()
|
|
109
109
|
const dbPath = join(home, '.config', 'google-chrome', 'Default', 'Cookies')
|
|
110
110
|
createCookieDbWithPlaintext(dbPath, 'opensoma-dev-session', 0, 'opensoma.dev')
|
|
@@ -113,7 +113,7 @@ describe('TokenExtractor', () => {
|
|
|
113
113
|
expect(await extractor.extract()).toEqual({ sessionCookie: 'opensoma-dev-session' })
|
|
114
114
|
})
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
it('decrypts an encrypted cookie from the opensoma.dev host on Linux', async () => {
|
|
117
117
|
const home = await makeTempDir()
|
|
118
118
|
const dbPath = join(home, '.config', 'google-chrome', 'Default', 'Cookies')
|
|
119
119
|
const encrypted = encryptLinuxCookie('opensoma-dev-encrypted')
|
|
@@ -123,7 +123,7 @@ describe('TokenExtractor', () => {
|
|
|
123
123
|
expect(await extractor.extract()).toEqual({ sessionCookie: 'opensoma-dev-encrypted' })
|
|
124
124
|
})
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
it('decrypts an encrypted cookie on Linux', async () => {
|
|
127
127
|
const home = await makeTempDir()
|
|
128
128
|
const dbPath = join(home, '.config', 'google-chrome', 'Default', 'Cookies')
|
|
129
129
|
const encrypted = encryptLinuxCookie('decrypted-session')
|
|
@@ -133,7 +133,7 @@ describe('TokenExtractor', () => {
|
|
|
133
133
|
expect(await extractor.extract()).toEqual({ sessionCookie: 'decrypted-session' })
|
|
134
134
|
})
|
|
135
135
|
|
|
136
|
-
|
|
136
|
+
it('extracts a plaintext cookie when running under Node.js (compiled ESM)', async () => {
|
|
137
137
|
execSync('bun run build', { cwd: join(import.meta.dir, '..'), stdio: 'pipe' })
|
|
138
138
|
const home = await makeTempDir()
|
|
139
139
|
const dbPath = join(home, '.config', 'google-chrome', 'Default', 'Cookies')
|
|
@@ -144,7 +144,7 @@ describe('TokenExtractor', () => {
|
|
|
144
144
|
expect(JSON.parse(result.trim())).toEqual({ sessionCookie: 'node-test-session' })
|
|
145
145
|
})
|
|
146
146
|
|
|
147
|
-
|
|
147
|
+
it('extracts an encrypted cookie when running under Node.js (compiled ESM)', async () => {
|
|
148
148
|
execSync('bun run build', { cwd: join(import.meta.dir, '..'), stdio: 'pipe' })
|
|
149
149
|
const home = await makeTempDir()
|
|
150
150
|
const dbPath = join(home, '.config', 'google-chrome', 'Default', 'Cookies')
|
package/src/toz-http.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { afterEach, describe, expect,
|
|
1
|
+
import { afterEach, describe, expect, it, mock } from 'bun:test'
|
|
2
2
|
|
|
3
3
|
import { TOZ_BASE_URL } from './constants'
|
|
4
4
|
import { TozHttp } from './toz-http'
|
|
@@ -11,7 +11,7 @@ afterEach(() => {
|
|
|
11
11
|
})
|
|
12
12
|
|
|
13
13
|
describe('TozHttp', () => {
|
|
14
|
-
|
|
14
|
+
it('seeds JSESSIONID by GETting index.htm during bootstrap', async () => {
|
|
15
15
|
const fetchMock = mock(async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
16
16
|
expect(String(input)).toBe(`${TOZ_BASE_URL}/index.htm`)
|
|
17
17
|
expect(init?.method).toBe('GET')
|
|
@@ -26,7 +26,7 @@ describe('TozHttp', () => {
|
|
|
26
26
|
expect(fetchMock).toHaveBeenCalledTimes(1)
|
|
27
27
|
})
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
it('skips bootstrap when JSESSIONID is already set', async () => {
|
|
30
30
|
const fetchMock = mock(async () => createResponse('ok'))
|
|
31
31
|
globalThis.fetch = fetchMock as typeof fetch
|
|
32
32
|
|
|
@@ -37,7 +37,7 @@ describe('TozHttp', () => {
|
|
|
37
37
|
expect(http.getSessionCookie()).toBe('EXISTING')
|
|
38
38
|
})
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
it('sends URL-encoded POST bodies with the required headers and cookie', async () => {
|
|
41
41
|
const fetchMock = mock(async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
42
42
|
expect(String(input)).toBe(`${TOZ_BASE_URL}/ajaxGetEnableBoothes.htm`)
|
|
43
43
|
expect(init?.method).toBe('POST')
|
|
@@ -65,7 +65,7 @@ describe('TozHttp', () => {
|
|
|
65
65
|
expect(text).toBe('[]')
|
|
66
66
|
})
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
it('parses the JSON body returned by postJson', async () => {
|
|
69
69
|
globalThis.fetch = mock(async () => createResponse('{"result":"SUCCESS","resultMsg":"abc"}')) as typeof fetch
|
|
70
70
|
|
|
71
71
|
const http = new TozHttp({ sessionCookie: 'ABC' })
|
|
@@ -74,25 +74,25 @@ describe('TozHttp', () => {
|
|
|
74
74
|
expect(json).toEqual({ result: 'SUCCESS', resultMsg: 'abc' })
|
|
75
75
|
})
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
it('trims the plain-text body returned by postText', async () => {
|
|
78
78
|
globalThis.fetch = mock(async () => createResponse('SUCCESS\n')) as typeof fetch
|
|
79
79
|
|
|
80
80
|
const http = new TozHttp({ sessionCookie: 'ABC' })
|
|
81
81
|
expect(await http.postText('/ajaxHpVerify.htm', {})).toBe('SUCCESS')
|
|
82
82
|
})
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
it('strips the "JSESSIONID=" prefix when constructed with a full cookie string', async () => {
|
|
85
85
|
const http = new TozHttp({ sessionCookie: 'JSESSIONID=XYZ789' })
|
|
86
86
|
expect(http.getSessionCookie()).toBe('XYZ789')
|
|
87
87
|
expect(http.getCookies()).toEqual({ JSESSIONID: 'XYZ789' })
|
|
88
88
|
})
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
it('preserves every cookie when constructed with a cookies map', async () => {
|
|
91
91
|
const http = new TozHttp({ cookies: { JSESSIONID: 'A', other: 'B' } })
|
|
92
92
|
expect(http.getCookies()).toEqual({ JSESSIONID: 'A', other: 'B' })
|
|
93
93
|
})
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
it('throws on a non-2xx response', async () => {
|
|
96
96
|
globalThis.fetch = mock(async () =>
|
|
97
97
|
createResponse('Server Error', [], 'text/html', { status: 500 }),
|
|
98
98
|
) as typeof fetch
|
|
@@ -101,7 +101,7 @@ describe('TozHttp', () => {
|
|
|
101
101
|
await expect(http.get('/index.htm')).rejects.toThrow(/HTTP 500/)
|
|
102
102
|
})
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
it('refuses to follow cross-origin redirects to prevent cookie leaks', async () => {
|
|
105
105
|
globalThis.fetch = mock(async (input: RequestInfo | URL) => {
|
|
106
106
|
const url = String(input)
|
|
107
107
|
if (url.endsWith('/index.htm')) {
|
|
@@ -114,7 +114,7 @@ describe('TozHttp', () => {
|
|
|
114
114
|
await expect(http.get('/index.htm')).rejects.toThrow(/cross-origin redirect/)
|
|
115
115
|
})
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
it('follows same-origin redirects normally', async () => {
|
|
118
118
|
let hit = 0
|
|
119
119
|
globalThis.fetch = mock(async (input: RequestInfo | URL) => {
|
|
120
120
|
const url = String(input)
|