@youversion/platform-core 0.8.2 → 0.9.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/src/URLBuilder.ts DELETED
@@ -1,50 +0,0 @@
1
- import type { SignInWithYouVersionPermissionValues } from './types';
2
- import { YouVersionPlatformConfiguration } from './YouVersionPlatformConfiguration';
3
-
4
- export class URLBuilder {
5
- private static get baseURL(): URL {
6
- return new URL(`https://${YouVersionPlatformConfiguration.apiHost}`);
7
- }
8
-
9
- static authURL(
10
- appKey: string,
11
- requiredPermissions: Set<SignInWithYouVersionPermissionValues> = new Set<SignInWithYouVersionPermissionValues>(),
12
- optionalPermissions: Set<SignInWithYouVersionPermissionValues> = new Set<SignInWithYouVersionPermissionValues>(),
13
- ): URL {
14
- if (typeof appKey !== 'string' || appKey.trim().length === 0) {
15
- throw new Error('appKey must be a non-empty string');
16
- }
17
-
18
- try {
19
- const url = new URL(this.baseURL);
20
- url.pathname = '/auth/login';
21
-
22
- // Add query parameters
23
- const searchParams = new URLSearchParams();
24
- searchParams.append('APP_KEY', appKey);
25
- searchParams.append('language', 'en'); // TODO: load from system
26
-
27
- if (requiredPermissions.size > 0) {
28
- const requiredList = Array.from(requiredPermissions).map((p) => p.toString());
29
- searchParams.append('required_perms', requiredList.join(','));
30
- }
31
-
32
- if (optionalPermissions.size > 0) {
33
- const optionalList = Array.from(optionalPermissions).map((p) => p.toString());
34
- searchParams.append('opt_perms', optionalList.join(','));
35
- }
36
-
37
- const installationId = YouVersionPlatformConfiguration.installationId;
38
- if (installationId) {
39
- searchParams.append('x-yvp-installation-id', installationId);
40
- }
41
-
42
- url.search = searchParams.toString();
43
- return url;
44
- } catch (error) {
45
- throw new Error(
46
- `Failed to construct auth URL: ${error instanceof Error ? error.message : 'Unknown error'}`,
47
- );
48
- }
49
- }
50
- }
@@ -1,190 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { URLBuilder } from '../URLBuilder';
3
- import { YouVersionPlatformConfiguration } from '../YouVersionPlatformConfiguration';
4
- import type { SignInWithYouVersionPermissionValues } from '../types';
5
-
6
- describe('URLBuilder - Input Validation', () => {
7
- let originalApiHost: string;
8
- let originalInstallationId: string | null;
9
-
10
- beforeEach(() => {
11
- // Store original config
12
- originalApiHost = YouVersionPlatformConfiguration.apiHost;
13
- originalInstallationId = YouVersionPlatformConfiguration.installationId;
14
-
15
- // Set test config
16
- const apiHost = process.env.YVP_API_HOST;
17
- if (!apiHost) {
18
- throw new Error('YVP_API_HOST environment variable must be set for URLBuilder tests.');
19
- }
20
- YouVersionPlatformConfiguration.apiHost = apiHost;
21
- YouVersionPlatformConfiguration.installationId = 'test-installation-id';
22
- });
23
-
24
- afterEach(() => {
25
- // Restore original config
26
- YouVersionPlatformConfiguration.apiHost = originalApiHost;
27
- YouVersionPlatformConfiguration.installationId = originalInstallationId;
28
- });
29
-
30
- describe('authURL - appKey validation', () => {
31
- it('should throw error for empty string appKey', () => {
32
- expect(() => {
33
- URLBuilder.authURL('');
34
- }).toThrow('appKey must be a non-empty string');
35
- });
36
-
37
- it('should throw error for whitespace-only appKey', () => {
38
- expect(() => {
39
- URLBuilder.authURL(' ');
40
- }).toThrow('appKey must be a non-empty string');
41
- });
42
-
43
- it('should throw error for tab/newline-only appKey', () => {
44
- expect(() => {
45
- URLBuilder.authURL('\t\n ');
46
- }).toThrow('appKey must be a non-empty string');
47
- });
48
-
49
- it('should throw descriptive error message', () => {
50
- try {
51
- URLBuilder.authURL('');
52
- // Should not reach here
53
- expect.fail('Should have thrown an error');
54
- } catch (error) {
55
- expect(error).toBeInstanceOf(Error);
56
- expect((error as Error).message).toContain('appKey');
57
- expect((error as Error).message).toContain('non-empty string');
58
- }
59
- });
60
-
61
- it('should accept valid non-empty appKey', () => {
62
- const url = URLBuilder.authURL('valid-app-key');
63
-
64
- expect(url).toBeInstanceOf(URL);
65
- expect(url.hostname.endsWith('.youversion.com')).toBe(true);
66
- expect(url.pathname).toBe('/auth/login');
67
- expect(url.searchParams.get('APP_KEY')).toBe('valid-app-key');
68
- });
69
-
70
- it('should trim and accept appKey with surrounding whitespace', () => {
71
- // Note: The validation checks trim().length, so this should pass
72
- const url = URLBuilder.authURL(' valid-app-key ');
73
-
74
- expect(url).toBeInstanceOf(URL);
75
- // The actual value passed has whitespace preserved in the URL
76
- expect(url.searchParams.get('APP_KEY')).toBe(' valid-app-key ');
77
- });
78
-
79
- it('should accept appKey with special characters', () => {
80
- const specialAppKey = 'app-key_123.test';
81
- const url = URLBuilder.authURL(specialAppKey);
82
-
83
- expect(url.searchParams.get('APP_KEY')).toBe(specialAppKey);
84
- });
85
- });
86
-
87
- describe('authURL - URL construction', () => {
88
- it('should construct correct base URL and pathname', () => {
89
- const url = URLBuilder.authURL('test-app');
90
-
91
- expect(url.protocol).toBe('https:');
92
- expect(url.hostname.endsWith('.youversion.com')).toBe(true);
93
- expect(url.pathname).toBe('/auth/login');
94
- });
95
-
96
- it('should include APP_KEY in query parameters', () => {
97
- const url = URLBuilder.authURL('my-app-key');
98
-
99
- expect(url.searchParams.get('APP_KEY')).toBe('my-app-key');
100
- });
101
-
102
- it('should include default language parameter', () => {
103
- const url = URLBuilder.authURL('test-app');
104
-
105
- expect(url.searchParams.get('language')).toBe('en');
106
- });
107
-
108
- it('should include installation ID when configured', () => {
109
- YouVersionPlatformConfiguration.installationId = 'test-installation-123';
110
- const url = URLBuilder.authURL('test-app');
111
-
112
- expect(url.searchParams.get('x-yvp-installation-id')).toBe('test-installation-123');
113
- });
114
-
115
- it('should not include installation ID when not configured', () => {
116
- YouVersionPlatformConfiguration.installationId = null;
117
- const url = URLBuilder.authURL('test-app');
118
-
119
- expect(url.searchParams.get('x-yvp-installation-id')).toBeNull();
120
- });
121
-
122
- it('should include required permissions when provided', () => {
123
- const permissions = new Set<SignInWithYouVersionPermissionValues>(['bibles', 'votd']);
124
- const url = URLBuilder.authURL('test-app', permissions);
125
-
126
- const requiredPerms = url.searchParams.get('required_perms');
127
- expect(requiredPerms).toBeTruthy();
128
- expect(requiredPerms?.split(',')).toContain('bibles');
129
- expect(requiredPerms?.split(',')).toContain('votd');
130
- });
131
-
132
- it('should include optional permissions when provided', () => {
133
- const optionalPermissions = new Set<SignInWithYouVersionPermissionValues>(['demographics']);
134
- const url = URLBuilder.authURL('test-app', new Set(), optionalPermissions);
135
-
136
- const optPerms = url.searchParams.get('opt_perms');
137
- expect(optPerms).toBe('demographics');
138
- });
139
-
140
- it('should include both required and optional permissions', () => {
141
- const required = new Set<SignInWithYouVersionPermissionValues>(['bibles']);
142
- const optional = new Set<SignInWithYouVersionPermissionValues>(['bible_activity']);
143
- const url = URLBuilder.authURL('test-app', required, optional);
144
-
145
- expect(url.searchParams.get('required_perms')).toBe('bibles');
146
- expect(url.searchParams.get('opt_perms')).toBe('bible_activity');
147
- });
148
-
149
- it('should handle empty permission sets', () => {
150
- const url = URLBuilder.authURL('test-app', new Set(), new Set());
151
-
152
- expect(url.searchParams.get('required_perms')).toBeNull();
153
- expect(url.searchParams.get('opt_perms')).toBeNull();
154
- });
155
- });
156
-
157
- describe('Error handling', () => {
158
- it('should throw errors instead of returning null for invalid appKey', () => {
159
- // Verify that the method throws, not returns null
160
- let threwError = false;
161
- let returnValue: any;
162
-
163
- try {
164
- returnValue = URLBuilder.authURL('');
165
- } catch {
166
- threwError = true;
167
- }
168
-
169
- expect(threwError).toBe(true);
170
- expect(returnValue).toBeUndefined();
171
- });
172
-
173
- it('should wrap URL construction errors with descriptive message for authURL', () => {
174
- // This is hard to trigger, but we can at least verify the pattern exists
175
- // by checking that valid inputs don't trigger the catch block
176
- expect(() => {
177
- URLBuilder.authURL('valid-app-key');
178
- }).not.toThrow(/Failed to construct auth URL/);
179
- });
180
- });
181
-
182
- describe('Return type validation', () => {
183
- it('authURL should return URL object (not null)', () => {
184
- const result = URLBuilder.authURL('test-app');
185
-
186
- expect(result).toBeInstanceOf(URL);
187
- expect(result).not.toBeNull();
188
- });
189
- });
190
- });