@codeclimbers/server 0.0.1

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 (175) hide show
  1. package/README.md +73 -0
  2. package/dist/index.d.ts +4 -0
  3. package/dist/index.js +22 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/src/app.module.d.ts +4 -0
  6. package/dist/src/app.module.js +49 -0
  7. package/dist/src/app.module.js.map +1 -0
  8. package/dist/src/assets/startup.plist.d.ts +1 -0
  9. package/dist/src/assets/startup.plist.js +41 -0
  10. package/dist/src/assets/startup.plist.js.map +1 -0
  11. package/dist/src/common/infrastructure/http/controllers/health.controller.d.ts +5 -0
  12. package/dist/src/common/infrastructure/http/controllers/health.controller.js +29 -0
  13. package/dist/src/common/infrastructure/http/controllers/health.controller.js.map +1 -0
  14. package/dist/src/common/infrastructure/http/middleware/requestlogger.middleware.d.ts +5 -0
  15. package/dist/src/common/infrastructure/http/middleware/requestlogger.middleware.js +27 -0
  16. package/dist/src/common/infrastructure/http/middleware/requestlogger.middleware.js.map +1 -0
  17. package/dist/src/main.d.ts +2 -0
  18. package/dist/src/main.js +44 -0
  19. package/dist/src/main.js.map +1 -0
  20. package/dist/src/sentry.d.ts +1 -0
  21. package/dist/src/sentry.js +13 -0
  22. package/dist/src/sentry.js.map +1 -0
  23. package/dist/src/v1/activities/activities.service.d.ts +21 -0
  24. package/dist/src/v1/activities/activities.service.js +174 -0
  25. package/dist/src/v1/activities/activities.service.js.map +1 -0
  26. package/dist/src/v1/activities/pulse.controller.d.ts +19 -0
  27. package/dist/src/v1/activities/pulse.controller.js +92 -0
  28. package/dist/src/v1/activities/pulse.controller.js.map +1 -0
  29. package/dist/src/v1/activities/wakatimeProxy.controller.d.ts +13 -0
  30. package/dist/src/v1/activities/wakatimeProxy.controller.js +64 -0
  31. package/dist/src/v1/activities/wakatimeProxy.controller.js.map +1 -0
  32. package/dist/src/v1/database/__tests__/knex.test.d.ts +1 -0
  33. package/dist/src/v1/database/__tests__/knex.test.js +18 -0
  34. package/dist/src/v1/database/__tests__/knex.test.js.map +1 -0
  35. package/dist/src/v1/database/__tests__/pulse.repo.test.d.ts +1 -0
  36. package/dist/src/v1/database/__tests__/pulse.repo.test.js +141 -0
  37. package/dist/src/v1/database/__tests__/pulse.repo.test.js.map +1 -0
  38. package/dist/src/v1/database/knex.d.ts +5 -0
  39. package/dist/src/v1/database/knex.js +87 -0
  40. package/dist/src/v1/database/knex.js.map +1 -0
  41. package/dist/src/v1/database/migrations.d.ts +1 -0
  42. package/dist/src/v1/database/migrations.js +16 -0
  43. package/dist/src/v1/database/migrations.js.map +1 -0
  44. package/dist/src/v1/database/pulse.repo.d.ts +17 -0
  45. package/dist/src/v1/database/pulse.repo.js +115 -0
  46. package/dist/src/v1/database/pulse.repo.js.map +1 -0
  47. package/dist/src/v1/database/queries/getCategoryTimeOverview.sql +6 -0
  48. package/dist/src/v1/database/queries/getLongestDayInRangeMinutes.sql +11 -0
  49. package/dist/src/v1/database/queries/getStatusBarDetails.sql +42 -0
  50. package/dist/src/v1/dtos/createWakatimePulse.dto.d.ts +19 -0
  51. package/dist/src/v1/dtos/createWakatimePulse.dto.js +97 -0
  52. package/dist/src/v1/dtos/createWakatimePulse.dto.js.map +1 -0
  53. package/dist/src/v1/dtos/getCategoryTimeOverview.dto.d.ts +4 -0
  54. package/dist/src/v1/dtos/getCategoryTimeOverview.dto.js +25 -0
  55. package/dist/src/v1/dtos/getCategoryTimeOverview.dto.js.map +1 -0
  56. package/dist/src/v1/dtos/getWeekOverview.dto.d.ts +3 -0
  57. package/dist/src/v1/dtos/getWeekOverview.dto.js +21 -0
  58. package/dist/src/v1/dtos/getWeekOverview.dto.js.map +1 -0
  59. package/dist/src/v1/startup/darwinStartup.service.d.ts +8 -0
  60. package/dist/src/v1/startup/darwinStartup.service.js +114 -0
  61. package/dist/src/v1/startup/darwinStartup.service.js.map +1 -0
  62. package/dist/src/v1/startup/linuxStartup.service.d.ts +8 -0
  63. package/dist/src/v1/startup/linuxStartup.service.js +104 -0
  64. package/dist/src/v1/startup/linuxStartup.service.js.map +1 -0
  65. package/dist/src/v1/startup/startup.controller.d.ts +8 -0
  66. package/dist/src/v1/startup/startup.controller.js +46 -0
  67. package/dist/src/v1/startup/startup.controller.js.map +1 -0
  68. package/dist/src/v1/startup/startup.util.d.ts +5 -0
  69. package/dist/src/v1/startup/startup.util.js +19 -0
  70. package/dist/src/v1/startup/startup.util.js.map +1 -0
  71. package/dist/src/v1/startup/startupService.factory.d.ts +9 -0
  72. package/dist/src/v1/startup/startupService.factory.js +41 -0
  73. package/dist/src/v1/startup/startupService.factory.js.map +1 -0
  74. package/dist/src/v1/startup/unsupportedStartup.service.d.ts +6 -0
  75. package/dist/src/v1/startup/unsupportedStartup.service.js +29 -0
  76. package/dist/src/v1/startup/unsupportedStartup.service.js.map +1 -0
  77. package/dist/src/v1/v1.module.d.ts +2 -0
  78. package/dist/src/v1/v1.module.js +41 -0
  79. package/dist/src/v1/v1.module.js.map +1 -0
  80. package/dist/tsconfig.build.tsbuildinfo +1 -0
  81. package/dist/utils/__tests__/activites.util.test.d.ts +1 -0
  82. package/dist/utils/__tests__/activites.util.test.js +18 -0
  83. package/dist/utils/__tests__/activites.util.test.js.map +1 -0
  84. package/dist/utils/__tests__/helpers.util.test.d.ts +1 -0
  85. package/dist/utils/__tests__/helpers.util.test.js +120 -0
  86. package/dist/utils/__tests__/helpers.util.test.js.map +1 -0
  87. package/dist/utils/__tests__/wakatime.util.test.d.ts +1 -0
  88. package/dist/utils/__tests__/wakatime.util.test.js +210 -0
  89. package/dist/utils/__tests__/wakatime.util.test.js.map +1 -0
  90. package/dist/utils/activities.util.d.ts +15 -0
  91. package/dist/utils/activities.util.js +147 -0
  92. package/dist/utils/activities.util.js.map +1 -0
  93. package/dist/utils/allExceptions.filter.d.ts +7 -0
  94. package/dist/utils/allExceptions.filter.js +39 -0
  95. package/dist/utils/allExceptions.filter.js.map +1 -0
  96. package/dist/utils/codeClimberErrors.d.ts +16 -0
  97. package/dist/utils/codeClimberErrors.js +27 -0
  98. package/dist/utils/codeClimberErrors.js.map +1 -0
  99. package/dist/utils/constants.d.ts +1 -0
  100. package/dist/utils/constants.js +5 -0
  101. package/dist/utils/constants.js.map +1 -0
  102. package/dist/utils/environment.util.d.ts +1 -0
  103. package/dist/utils/environment.util.js +6 -0
  104. package/dist/utils/environment.util.js.map +1 -0
  105. package/dist/utils/helpers.util.d.ts +7 -0
  106. package/dist/utils/helpers.util.js +116 -0
  107. package/dist/utils/helpers.util.js.map +1 -0
  108. package/dist/utils/node.util.d.ts +7 -0
  109. package/dist/utils/node.util.js +26 -0
  110. package/dist/utils/node.util.js.map +1 -0
  111. package/dist/utils/sql.util.d.ts +1 -0
  112. package/dist/utils/sql.util.js +11 -0
  113. package/dist/utils/sql.util.js.map +1 -0
  114. package/dist/utils/sqlReader.util.d.ts +5 -0
  115. package/dist/utils/sqlReader.util.js +34 -0
  116. package/dist/utils/sqlReader.util.js.map +1 -0
  117. package/dist/utils/wakatime.util.d.ts +6 -0
  118. package/dist/utils/wakatime.util.js +63 -0
  119. package/dist/utils/wakatime.util.js.map +1 -0
  120. package/index.ts +5 -0
  121. package/jest.config.js +8 -0
  122. package/knexfile.js +14 -0
  123. package/nest-cli.json +15 -0
  124. package/package.json +77 -0
  125. package/src/app.module.ts +37 -0
  126. package/src/assets/startup.plist.ts +36 -0
  127. package/src/common/infrastructure/http/controllers/health.controller.ts +9 -0
  128. package/src/common/infrastructure/http/middleware/requestlogger.middleware.ts +22 -0
  129. package/src/main.ts +51 -0
  130. package/src/sentry.ts +14 -0
  131. package/src/types/activities.api.d.ts +18 -0
  132. package/src/types/time.api.d.ts +23 -0
  133. package/src/types/utils.d.ts +8 -0
  134. package/src/types/wakatimeProxy.api.d.ts +55 -0
  135. package/src/v1/activities/activities.service.ts +213 -0
  136. package/src/v1/activities/pulse.controller.ts +68 -0
  137. package/src/v1/activities/wakatimeProxy.controller.ts +33 -0
  138. package/src/v1/database/__tests__/knex.test.ts +18 -0
  139. package/src/v1/database/__tests__/pulse.repo.test.ts +209 -0
  140. package/src/v1/database/knex.ts +107 -0
  141. package/src/v1/database/migrations.ts +13 -0
  142. package/src/v1/database/models/pulse.d.ts +24 -0
  143. package/src/v1/database/pulse.repo.ts +132 -0
  144. package/src/v1/database/queries/getCategoryTimeOverview.sql +6 -0
  145. package/src/v1/database/queries/getLongestDayInRangeMinutes.sql +11 -0
  146. package/src/v1/database/queries/getStatusBarDetails.sql +42 -0
  147. package/src/v1/dtos/createWakatimePulse.dto.ts +66 -0
  148. package/src/v1/dtos/getCategoryTimeOverview.dto.ts +9 -0
  149. package/src/v1/dtos/getWeekOverview.dto.ts +6 -0
  150. package/src/v1/startup/darwinStartup.service.ts +114 -0
  151. package/src/v1/startup/linuxStartup.service.ts +101 -0
  152. package/src/v1/startup/startup.controller.ts +23 -0
  153. package/src/v1/startup/startup.util.ts +21 -0
  154. package/src/v1/startup/startupService.factory.ts +28 -0
  155. package/src/v1/startup/unsupportedStartup.service.ts +21 -0
  156. package/src/v1/v1.module.ts +28 -0
  157. package/test/app.e2e-spec.ts +24 -0
  158. package/test/jest-e2e.json +9 -0
  159. package/test/jest.globalSetup.js +15 -0
  160. package/test/jest.globalTeardown.js +8 -0
  161. package/tsconfig.build.json +4 -0
  162. package/tsconfig.json +22 -0
  163. package/utils/__tests__/activites.util.test.ts +18 -0
  164. package/utils/__tests__/helpers.util.test.ts +155 -0
  165. package/utils/__tests__/wakatime.util.test.ts +222 -0
  166. package/utils/activities.util.ts +193 -0
  167. package/utils/allExceptions.filter.ts +43 -0
  168. package/utils/codeClimberErrors.ts +32 -0
  169. package/utils/constants.ts +1 -0
  170. package/utils/environment.util.ts +1 -0
  171. package/utils/helpers.util.ts +131 -0
  172. package/utils/node.util.ts +29 -0
  173. package/utils/sql.util.ts +13 -0
  174. package/utils/sqlReader.util.ts +49 -0
  175. package/utils/wakatime.util.ts +78 -0
@@ -0,0 +1,28 @@
1
+ import { Injectable } from '@nestjs/common'
2
+ import { DarwinStartupService } from './darwinStartup.service'
3
+ import { UnsupportedStartupService } from './unsupportedStartup.service'
4
+
5
+ @Injectable()
6
+ export class StartupServiceFactory {
7
+ constructor(
8
+ private readonly darwinStartupService: DarwinStartupService,
9
+ private readonly unsupportedStartupService: UnsupportedStartupService,
10
+ ) {}
11
+
12
+ getStartupService(): CodeClimbers.StartupService {
13
+ const os = process.platform
14
+ switch (os) {
15
+ case 'darwin':
16
+ return this.darwinStartupService
17
+ default:
18
+ return this.unsupportedStartupService
19
+ }
20
+ }
21
+
22
+ static buildStartupService(): CodeClimbers.StartupService {
23
+ return new StartupServiceFactory(
24
+ new DarwinStartupService(),
25
+ new UnsupportedStartupService(),
26
+ ).getStartupService()
27
+ }
28
+ }
@@ -0,0 +1,21 @@
1
+ import { Injectable, Logger } from '@nestjs/common'
2
+
3
+ @Injectable()
4
+ export class UnsupportedStartupService implements CodeClimbers.StartupService {
5
+ async enableStartup() {
6
+ Logger.error(`Unsupported operating system: ${process.platform}`)
7
+ }
8
+
9
+ async disableStartup() {
10
+ Logger.error(`Unsupported operating system: ${process.platform}`)
11
+ }
12
+
13
+ // cleanly separate implementation code for each environment. If I'm working on windows, I see all the implementation around startup
14
+ async launchAndEnableStartup() {
15
+ Logger.error(`Unsupported operating system: ${process.platform}`)
16
+ }
17
+
18
+ async closeAndDisableStartup() {
19
+ Logger.error(`Unsupported operating system: ${process.platform}`)
20
+ }
21
+ }
@@ -0,0 +1,28 @@
1
+ import { Module } from '@nestjs/common'
2
+ import { HealthController } from '../common/infrastructure/http/controllers/health.controller'
3
+ import { StartupController } from './startup/startup.controller'
4
+ import { ActivitiesService } from './activities/activities.service'
5
+ import { PulseController } from './activities/pulse.controller'
6
+ import { WakatimeController } from './activities/wakatimeProxy.controller'
7
+ import { PulseRepo } from './database/pulse.repo'
8
+ import { DarwinStartupService } from './startup/darwinStartup.service'
9
+ import { StartupServiceFactory } from './startup/startupService.factory'
10
+ import { UnsupportedStartupService } from './startup/unsupportedStartup.service'
11
+
12
+ @Module({
13
+ imports: [],
14
+ controllers: [
15
+ HealthController,
16
+ PulseController,
17
+ WakatimeController,
18
+ StartupController,
19
+ ],
20
+ providers: [
21
+ ActivitiesService,
22
+ StartupServiceFactory,
23
+ PulseRepo,
24
+ UnsupportedStartupService,
25
+ DarwinStartupService,
26
+ ],
27
+ })
28
+ export class V1Module {}
@@ -0,0 +1,24 @@
1
+ import { Test, TestingModule } from '@nestjs/testing'
2
+ import { INestApplication } from '@nestjs/common'
3
+ import * as request from 'supertest'
4
+ import { AppModule } from './../src/app.module'
5
+
6
+ describe('AppController (e2e)', () => {
7
+ let app: INestApplication
8
+
9
+ beforeEach(async () => {
10
+ const moduleFixture: TestingModule = await Test.createTestingModule({
11
+ imports: [AppModule],
12
+ }).compile()
13
+
14
+ app = moduleFixture.createNestApplication()
15
+ await app.init()
16
+ })
17
+
18
+ it('/ (GET)', () => {
19
+ return request(app.getHttpServer())
20
+ .get('/')
21
+ .expect(200)
22
+ .expect('Hello World!')
23
+ })
24
+ })
@@ -0,0 +1,9 @@
1
+ {
2
+ "moduleFileExtensions": ["js", "json", "ts"],
3
+ "rootDir": ".",
4
+ "testEnvironment": "node",
5
+ "testRegex": ".e2e-spec.ts$",
6
+ "transform": {
7
+ "^.+\\.(t|j)s$": "ts-jest"
8
+ }
9
+ }
@@ -0,0 +1,15 @@
1
+ const { knex, SQL_LITE_TEST_FILE } = require('../src/v1/database/knex')
2
+
3
+ module.exports = async () => {
4
+ if (knex.client.config.connection.filename !== SQL_LITE_TEST_FILE) {
5
+ throw new Error('You are not using the test file')
6
+ }
7
+
8
+ console.log('\n======== SETUP - DROP ========')
9
+ await knex.migrate.rollback({}, true)
10
+
11
+ console.log('======== SETUP - MIGRATION ========')
12
+ await knex.migrate.latest()
13
+ console.log('======== SETUP - SEED ========')
14
+ await knex.seed.run()
15
+ }
@@ -0,0 +1,8 @@
1
+ const { knex } = require('../src/v1/database/knex')
2
+
3
+ module.exports = async () => {
4
+ // Don't disconnect the DB if in watch mode
5
+ if (process.env.JEST_WATCH) return
6
+
7
+ await knex.destroy()
8
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
4
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "jsx": "react-jsx",
5
+ "module": "CommonJS",
6
+ "declaration": true,
7
+ "removeComments": true,
8
+ "emitDecoratorMetadata": true,
9
+ "experimentalDecorators": true,
10
+ "allowSyntheticDefaultImports": true,
11
+ "target": "ES2021",
12
+ "sourceMap": true,
13
+ "outDir": "./dist",
14
+ "incremental": true,
15
+ "skipLibCheck": true,
16
+ "strictNullChecks": false,
17
+ "noImplicitAny": false,
18
+ "strictBindCallApply": false,
19
+ "forceConsistentCasingInFileNames": false,
20
+ "noFallthroughCasesInSwitch": false,
21
+ },
22
+ }
@@ -0,0 +1,18 @@
1
+ import activitiesUtil from '../activities.util'
2
+
3
+ describe('getSourceFromUserAgent', () => {
4
+ it(`should return 'vscode' as source of userAgents`, () => {
5
+ const userAgents = [
6
+ 'wakatime/v1.98.1 (windows-10.0.22631.3880-unknown) go1.22.5 vscode/1.91.1 vscode-climbers/0.0.0',
7
+ 'wakatime/v1.98.1 (windows-10.0.22631.3880-unknown) go1.22.5 vscode/1.91.1 vscode-wakatime/24.6.0',
8
+ 'wakatime/v1.98.3 (windows-10.0.22631.3880-unknown) go1.22.5 vscode/1.91.1 vscode-climbers/0.0.0',
9
+ 'wakatime/v1.98.3 (windows-10.0.22631.3880-unknown) go1.22.5 vscode/1.91.1 vscode-wakatime/24.6.0',
10
+ ]
11
+
12
+ const result = userAgents.map((userAgent) => {
13
+ return activitiesUtil.getSourceFromUserAgent(userAgent)
14
+ })
15
+
16
+ expect(result).toEqual(['vscode', 'vscode', 'vscode', 'vscode'])
17
+ })
18
+ })
@@ -0,0 +1,155 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import {
3
+ forOwn,
4
+ isPlainObject,
5
+ snakeCase,
6
+ camelCase,
7
+ maxBy,
8
+ minBy,
9
+ groupBy,
10
+ } from '../helpers.util'
11
+
12
+ describe('forOwn', () => {
13
+ it('should iterate over own enumerable properties', () => {
14
+ const obj = { a: 1, b: 2, c: 3 }
15
+ const result: [string, number][] = []
16
+
17
+ forOwn(obj, (value, key) => {
18
+ result.push([key, value])
19
+ })
20
+
21
+ expect(result).toEqual([
22
+ ['a', 1],
23
+ ['b', 2],
24
+ ['c', 3],
25
+ ])
26
+ })
27
+
28
+ it('should handle empty objects', () => {
29
+ const obj = {}
30
+ let count = 0
31
+
32
+ forOwn(obj, () => {
33
+ count++
34
+ })
35
+
36
+ expect(count).toBe(0)
37
+ })
38
+ })
39
+
40
+ describe('isPlainObject', () => {
41
+ it('should return true for plain objects', () => {
42
+ expect(isPlainObject({})).toBe(true)
43
+ expect(isPlainObject({ a: 1 })).toBe(true)
44
+ expect(isPlainObject(Object.create(null))).toBe(true)
45
+ })
46
+
47
+ it('should return false for non-plain objects', () => {
48
+ expect(isPlainObject(null)).toBe(false)
49
+ expect(isPlainObject(undefined)).toBe(false)
50
+ expect(isPlainObject(123)).toBe(false)
51
+ expect(isPlainObject('string')).toBe(false)
52
+ expect(isPlainObject([])).toBe(false)
53
+ expect(isPlainObject(new Date())).toBe(false)
54
+ })
55
+ })
56
+
57
+ describe('snakeCase', () => {
58
+ it('should convert strings to snake case', () => {
59
+ expect(snakeCase('hello world')).toBe('hello_world')
60
+ expect(snakeCase('helloWorld')).toBe('hello_world')
61
+ expect(snakeCase('HelloWorld')).toBe('hello_world')
62
+ expect(snakeCase('hello-world')).toBe('hello_world')
63
+ })
64
+
65
+ it('should handle non-string inputs', () => {
66
+ expect(snakeCase(null as any)).toBe('')
67
+ expect(snakeCase(123 as any)).toBe('')
68
+ })
69
+ })
70
+
71
+ describe('camelCase', () => {
72
+ it('should convert strings to camel case', () => {
73
+ expect(camelCase('hello world')).toBe('helloWorld')
74
+ expect(camelCase('hello-world')).toBe('helloWorld')
75
+ expect(camelCase('hello_world')).toBe('helloWorld')
76
+ expect(camelCase('Hello World')).toBe('helloWorld')
77
+ expect(camelCase('HELLO WORLD')).toBe('helloWorld')
78
+ })
79
+
80
+ it('should handle non-string inputs', () => {
81
+ expect(camelCase(null as any)).toBe('')
82
+ expect(camelCase(123 as any)).toBe('')
83
+ })
84
+ })
85
+
86
+ describe('maxBy', () => {
87
+ it('should return the maximum value by the given key', () => {
88
+ const arr = [
89
+ { a: 1, b: 3 },
90
+ { a: 2, b: 1 },
91
+ { a: 3, b: 2 },
92
+ ]
93
+
94
+ // row 3
95
+ // row 1Î
96
+ expect(maxBy(arr, (x) => x.a)).toEqual({ a: 3, b: 2 })
97
+ expect(maxBy(arr, (x) => x.b)).toEqual({ a: 1, b: 3 })
98
+ })
99
+
100
+ it('should return undefined for empty arrays', () => {
101
+ expect(
102
+ maxBy([], () => {
103
+ return
104
+ }),
105
+ ).toBeUndefined()
106
+ })
107
+ })
108
+
109
+ describe('minBy', () => {
110
+ it('should return the minimum value by the given key', () => {
111
+ const arr = [
112
+ { a: 1, b: 2 },
113
+ { a: 2, b: 1 },
114
+ { a: 3, b: 3 },
115
+ ]
116
+
117
+ expect(minBy(arr, (x) => x.a)).toEqual({ a: 1, b: 2 })
118
+ expect(minBy(arr, (x) => x.b)).toEqual({ a: 2, b: 1 })
119
+ })
120
+
121
+ it('should return undefined for empty arrays', () => {
122
+ expect(
123
+ minBy([], () => {
124
+ return
125
+ }),
126
+ ).toBeUndefined()
127
+ })
128
+ })
129
+
130
+ describe('groupBy', () => {
131
+ it('should group items by the given key', () => {
132
+ const arr = [
133
+ { a: 1, b: 2 },
134
+ { a: 2, b: 1 },
135
+ { a: 1, b: 3 },
136
+ ]
137
+
138
+ const result = groupBy(arr, 'a' as any)
139
+
140
+ expect(result).toEqual({
141
+ 1: [
142
+ { a: 1, b: 2 },
143
+ { a: 1, b: 3 },
144
+ ],
145
+ 2: [{ a: 2, b: 1 }],
146
+ })
147
+ })
148
+
149
+ it('should handle empty arrays', () => {
150
+ const arr: any[] = []
151
+ const result = groupBy(arr, 'a' as any)
152
+
153
+ expect(result).toEqual({})
154
+ })
155
+ })
@@ -0,0 +1,222 @@
1
+ import { IniConfig, parseIni, stringifyIni } from '../wakatime.util'
2
+
3
+ describe('parseIni', () => {
4
+ it('should parse a valid INI string with one section', () => {
5
+ const input = `
6
+ [section1]
7
+ key1 = value1
8
+ key2 = value2
9
+ `
10
+ const expected: IniConfig = {
11
+ section1: {
12
+ key1: 'value1',
13
+ key2: 'value2',
14
+ },
15
+ }
16
+ expect(parseIni(input)).toEqual(expected)
17
+ })
18
+
19
+ it('should parse a valid INI string with multiple sections', () => {
20
+ const input = `
21
+ [section1]
22
+ key1 = value1
23
+ key2 = value2
24
+
25
+ [section2]
26
+ key3 = value3
27
+ key4 = value4
28
+ `
29
+ const expected: IniConfig = {
30
+ section1: {
31
+ key1: 'value1',
32
+ key2: 'value2',
33
+ },
34
+ section2: {
35
+ key3: 'value3',
36
+ key4: 'value4',
37
+ },
38
+ }
39
+ expect(parseIni(input)).toEqual(expected)
40
+ })
41
+
42
+ it('should handle empty input', () => {
43
+ const input = ''
44
+ const expected: IniConfig = {}
45
+ expect(parseIni(input)).toEqual(expected)
46
+ })
47
+
48
+ it('should handle input with only whitespace', () => {
49
+ const input = ' \n \t '
50
+ const expected: IniConfig = {}
51
+ expect(parseIni(input)).toEqual(expected)
52
+ })
53
+
54
+ it('should ignore lines without a section', () => {
55
+ const input = `
56
+ key1 = value1
57
+ [section1]
58
+ key2 = value2
59
+ `
60
+ const expected: IniConfig = {
61
+ section1: {
62
+ key2: 'value2',
63
+ },
64
+ }
65
+ expect(parseIni(input)).toEqual(expected)
66
+ })
67
+
68
+ it('should handle malformed lines within a section', () => {
69
+ const input = `
70
+ [section1]
71
+ key1 = value1
72
+ malformed line
73
+ key2 = value2
74
+ `
75
+ const expected: IniConfig = {
76
+ section1: {
77
+ key1: 'value1',
78
+ key2: 'value2',
79
+ },
80
+ }
81
+ expect(parseIni(input)).toEqual(expected)
82
+ })
83
+
84
+ it('should handle keys without values', () => {
85
+ const input = `
86
+ [section1]
87
+ key1 = value1
88
+ key2 =
89
+ key3 = value3
90
+ `
91
+ const expected: IniConfig = {
92
+ section1: {
93
+ key1: 'value1',
94
+ key3: 'value3',
95
+ },
96
+ }
97
+ expect(parseIni(input)).toEqual(expected)
98
+ })
99
+
100
+ it('should handle section names with spaces', () => {
101
+ const input = `
102
+ [Section One]
103
+ key1 = value1
104
+ [Section Two]
105
+ key2 = value2
106
+ `
107
+ const expected: IniConfig = {
108
+ 'Section One': {
109
+ key1: 'value1',
110
+ },
111
+ 'Section Two': {
112
+ key2: 'value2',
113
+ },
114
+ }
115
+ expect(parseIni(input)).toEqual(expected)
116
+ })
117
+ })
118
+
119
+ describe('stringifyIni', () => {
120
+ it('should return an empty string for empty input', () => {
121
+ const input: IniConfig = {}
122
+ const expected = '\n'
123
+ expect(stringifyIni(input)).toBe(expected)
124
+ })
125
+
126
+ it('should correctly stringify a single section with multiple entries', () => {
127
+ const input: IniConfig = {
128
+ section1: {
129
+ key1: 'value1',
130
+ key2: 'value2',
131
+ key3: 'value3',
132
+ },
133
+ }
134
+ const expected = `[section1]
135
+ key1 = value1
136
+ key2 = value2
137
+ key3 = value3\n`
138
+ expect(stringifyIni(input)).toBe(expected)
139
+ })
140
+
141
+ it('should correctly stringify multiple sections with multiple entries', () => {
142
+ const input: IniConfig = {
143
+ section1: {
144
+ key1: 'value1',
145
+ key2: 'value2',
146
+ },
147
+ section2: {
148
+ key3: 'value3',
149
+ key4: 'value4',
150
+ },
151
+ }
152
+ const expected = `[section1]
153
+ key1 = value1
154
+ key2 = value2
155
+
156
+ [section2]
157
+ key3 = value3
158
+ key4 = value4\n`
159
+ expect(stringifyIni(input)).toBe(expected)
160
+ })
161
+
162
+ it('should handle sections with no entries', () => {
163
+ const input: IniConfig = {
164
+ section1: {},
165
+ section2: {
166
+ key1: 'value1',
167
+ },
168
+ section3: {},
169
+ }
170
+ const expected = `[section1]
171
+
172
+
173
+ [section2]
174
+ key1 = value1
175
+
176
+ [section3]
177
+ \n`
178
+ expect(stringifyIni(input)).toBe(expected)
179
+ })
180
+
181
+ it('should handle a mix of sections with and without entries', () => {
182
+ const input: IniConfig = {
183
+ section1: {
184
+ key1: 'value1',
185
+ },
186
+ section2: {},
187
+ section3: {
188
+ key2: 'value2',
189
+ key3: 'value3',
190
+ },
191
+ section4: {},
192
+ }
193
+
194
+ const expected = `[section1]
195
+ key1 = value1
196
+
197
+ [section2]
198
+
199
+
200
+ [section3]
201
+ key2 = value2
202
+ key3 = value3
203
+
204
+ [section4]
205
+ \n`
206
+ console.log(stringifyIni(input))
207
+ expect(stringifyIni(input)).toBe(expected)
208
+ })
209
+
210
+ it('should handle values containing spaces', () => {
211
+ const input: IniConfig = {
212
+ section1: {
213
+ key1: 'value with spaces',
214
+ key2: 'another value with spaces',
215
+ },
216
+ }
217
+ const expected = `[section1]
218
+ key1 = value with spaces
219
+ key2 = another value with spaces\n`
220
+ expect(stringifyIni(input)).toBe(expected)
221
+ })
222
+ })