Skip to content

Commit 4678233

Browse files
committed
style comparison updates
1 parent 74f6ba3 commit 4678233

File tree

3 files changed

+183
-163
lines changed

3 files changed

+183
-163
lines changed

src/tools/style-comparison-tool/StyleComparisonTool.schema.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,37 @@ export const StyleComparisonSchema = z.object({
1313
),
1414
accessToken: z
1515
.string()
16+
.describe(
17+
'Mapbox public access token (required, must start with pk.* and have styles:read permission). Secret tokens (sk.*) cannot be used as they cannot be exposed in browser URLs. Please use a public token or create one with styles:read permission.'
18+
),
19+
noCache: z
20+
.boolean()
21+
.optional()
22+
.default(false)
23+
.describe(
24+
'Set to true if either style has been recently updated to bypass caching and see the latest changes immediately. Set to false (default) to use cached versions for better performance. Only use true during development when you need to see style updates.'
25+
),
26+
zoom: z
27+
.number()
28+
.optional()
29+
.describe(
30+
'Initial zoom level for the map view (0-22). If provided along with latitude and longitude, sets the initial map position.'
31+
),
32+
latitude: z
33+
.number()
34+
.min(-90)
35+
.max(90)
36+
.optional()
37+
.describe(
38+
'Latitude coordinate for the initial map center (-90 to 90). Must be provided together with longitude and zoom.'
39+
),
40+
longitude: z
41+
.number()
42+
.min(-180)
43+
.max(180)
1644
.optional()
1745
.describe(
18-
'Mapbox public access token (must start with pk.* and have styles:read permission). If not provided, will attempt to find an existing public token from your account'
46+
'Longitude coordinate for the initial map center (-180 to 180). Must be provided together with latitude and zoom.'
1947
)
2048
});
2149

src/tools/style-comparison-tool/StyleComparisonTool.test.ts

Lines changed: 123 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import { MapboxApiBasedTool } from '../MapboxApiBasedTool.js';
2-
import { ListTokensTool } from '../list-tokens-tool/ListTokensTool.js';
32
import { StyleComparisonTool } from './StyleComparisonTool.js';
43

54
describe('StyleComparisonTool', () => {
65
let tool: StyleComparisonTool;
7-
let mockListTokensTool: jest.SpyInstance;
86

97
beforeEach(() => {
108
tool = new StyleComparisonTool();
@@ -33,37 +31,19 @@ describe('StyleComparisonTool', () => {
3331
expect(url).toContain('after=mapbox%2Foutdoors-v12');
3432
});
3533

36-
it('should attempt to fetch public token when no token provided', async () => {
37-
mockListTokensTool = jest
38-
.spyOn(ListTokensTool.prototype, 'run')
39-
.mockResolvedValue({
40-
isError: false,
41-
content: [
42-
{
43-
type: 'text',
44-
text: JSON.stringify({
45-
tokens: [
46-
{
47-
token: 'pk.fetched.token',
48-
name: 'Public Token'
49-
}
50-
]
51-
})
52-
}
53-
]
54-
});
55-
34+
it('should require access token', async () => {
5635
const input = {
5736
before: 'mapbox/streets-v11',
5837
after: 'mapbox/satellite-v9'
38+
// Missing accessToken
5939
};
6040

61-
const result = await tool.run(input);
41+
const result = await tool.run(input as any);
6242

63-
expect(result.isError).toBe(false);
64-
expect(mockListTokensTool).toHaveBeenCalledWith({ usage: 'pk' });
65-
const url = (result.content[0] as { type: 'text'; text: string }).text;
66-
expect(url).toContain('access_token=pk.fetched.token');
43+
expect(result.isError).toBe(true);
44+
expect(
45+
(result.content[0] as { type: 'text'; text: string }).text
46+
).toContain('Required');
6747
});
6848

6949
it('should handle full style URLs', async () => {
@@ -101,54 +81,7 @@ describe('StyleComparisonTool', () => {
10181
expect(url).toContain('after=testuser%2Fstyle-id-2');
10282
});
10383

104-
it('should reject secret tokens and try to fetch public token', async () => {
105-
mockListTokensTool = jest
106-
.spyOn(ListTokensTool.prototype, 'run')
107-
.mockResolvedValue({
108-
isError: false,
109-
content: [
110-
{
111-
type: 'text',
112-
text: JSON.stringify({
113-
tokens: [
114-
{
115-
token: 'pk.fetched.public.token',
116-
name: 'Public Token'
117-
}
118-
]
119-
})
120-
}
121-
]
122-
});
123-
124-
const input = {
125-
before: 'mapbox/streets-v11',
126-
after: 'mapbox/outdoors-v12',
127-
accessToken: 'sk.secret.token'
128-
};
129-
130-
const result = await tool.run(input);
131-
132-
expect(result.isError).toBe(false);
133-
expect(mockListTokensTool).toHaveBeenCalledWith({ usage: 'pk' });
134-
const url = (result.content[0] as { type: 'text'; text: string }).text;
135-
expect(url).toContain('access_token=pk.fetched.public.token');
136-
expect(url).not.toContain('sk.secret.token');
137-
});
138-
139-
it('should error when secret token provided and no public token available', async () => {
140-
mockListTokensTool = jest
141-
.spyOn(ListTokensTool.prototype, 'run')
142-
.mockResolvedValue({
143-
isError: false,
144-
content: [
145-
{
146-
type: 'text',
147-
text: JSON.stringify({ tokens: [] })
148-
}
149-
]
150-
});
151-
84+
it('should reject secret tokens', async () => {
15285
const input = {
15386
before: 'mapbox/streets-v11',
15487
after: 'mapbox/outdoors-v12',
@@ -160,33 +93,25 @@ describe('StyleComparisonTool', () => {
16093
expect(result.isError).toBe(true);
16194
expect(
16295
(result.content[0] as { type: 'text'; text: string }).text
163-
).toContain('Secret tokens (sk.*) cannot be used for style comparison');
96+
).toContain('Invalid token type');
97+
expect(
98+
(result.content[0] as { type: 'text'; text: string }).text
99+
).toContain('Secret tokens (sk.*) cannot be exposed');
164100
});
165101

166-
it('should return error when no token available', async () => {
167-
mockListTokensTool = jest
168-
.spyOn(ListTokensTool.prototype, 'run')
169-
.mockResolvedValue({
170-
isError: false,
171-
content: [
172-
{
173-
type: 'text',
174-
text: JSON.stringify({ tokens: [] })
175-
}
176-
]
177-
});
178-
102+
it('should reject invalid token formats', async () => {
179103
const input = {
180104
before: 'mapbox/streets-v11',
181-
after: 'mapbox/outdoors-v12'
105+
after: 'mapbox/outdoors-v12',
106+
accessToken: 'invalid.token'
182107
};
183108

184109
const result = await tool.run(input);
185110

186111
expect(result.isError).toBe(true);
187112
expect(
188113
(result.content[0] as { type: 'text'; text: string }).text
189-
).toContain('No access token provided');
114+
).toContain('Invalid token type');
190115
});
191116

192117
it('should return error for style ID without valid username in token', async () => {
@@ -228,6 +153,113 @@ describe('StyleComparisonTool', () => {
228153
expect(url).toContain('before=user-name%2Fstyle-id-1');
229154
expect(url).toContain('after=user-name%2Fstyle-id-2');
230155
});
156+
157+
it('should include nocache parameter when noCache is true', async () => {
158+
const input = {
159+
before: 'mapbox/streets-v11',
160+
after: 'mapbox/outdoors-v12',
161+
accessToken: 'pk.test.token',
162+
noCache: true
163+
};
164+
165+
const result = await tool.run(input);
166+
167+
expect(result.isError).toBe(false);
168+
const url = (result.content[0] as { type: 'text'; text: string }).text;
169+
expect(url).toContain('nocache=true');
170+
});
171+
172+
it('should not include nocache parameter when noCache is false or undefined', async () => {
173+
const input = {
174+
before: 'mapbox/streets-v11',
175+
after: 'mapbox/outdoors-v12',
176+
accessToken: 'pk.test.token',
177+
noCache: false
178+
};
179+
180+
const result = await tool.run(input);
181+
182+
expect(result.isError).toBe(false);
183+
const url = (result.content[0] as { type: 'text'; text: string }).text;
184+
expect(url).not.toContain('nocache');
185+
186+
// Test with undefined (default)
187+
const inputWithoutNoCache = {
188+
before: 'mapbox/streets-v11',
189+
after: 'mapbox/outdoors-v12',
190+
accessToken: 'pk.test.token'
191+
};
192+
193+
const result2 = await tool.run(inputWithoutNoCache);
194+
expect(result2.isError).toBe(false);
195+
const url2 = (result2.content[0] as { type: 'text'; text: string }).text;
196+
expect(url2).not.toContain('nocache');
197+
});
198+
199+
it('should include hash fragment with map position when coordinates are provided', async () => {
200+
const input = {
201+
before: 'mapbox/streets-v11',
202+
after: 'mapbox/outdoors-v12',
203+
accessToken: 'pk.test.token',
204+
zoom: 5.72,
205+
latitude: 9.503,
206+
longitude: -67.473
207+
};
208+
209+
const result = await tool.run(input);
210+
211+
expect(result.isError).toBe(false);
212+
const url = (result.content[0] as { type: 'text'; text: string }).text;
213+
expect(url).toContain('#5.72/9.503/-67.473');
214+
});
215+
216+
it('should not include hash fragment when coordinates are incomplete', async () => {
217+
// Only zoom provided
218+
const input1 = {
219+
before: 'mapbox/streets-v11',
220+
after: 'mapbox/outdoors-v12',
221+
accessToken: 'pk.test.token',
222+
zoom: 10
223+
};
224+
225+
const result1 = await tool.run(input1);
226+
expect(result1.isError).toBe(false);
227+
const url1 = (result1.content[0] as { type: 'text'; text: string }).text;
228+
expect(url1).not.toContain('#');
229+
230+
// Only latitude and longitude, no zoom
231+
const input2 = {
232+
before: 'mapbox/streets-v11',
233+
after: 'mapbox/outdoors-v12',
234+
accessToken: 'pk.test.token',
235+
latitude: 40.7128,
236+
longitude: -74.006
237+
};
238+
239+
const result2 = await tool.run(input2);
240+
expect(result2.isError).toBe(false);
241+
const url2 = (result2.content[0] as { type: 'text'; text: string }).text;
242+
expect(url2).not.toContain('#');
243+
});
244+
245+
it('should handle both nocache and map position together', async () => {
246+
const input = {
247+
before: 'mapbox/streets-v11',
248+
after: 'mapbox/outdoors-v12',
249+
accessToken: 'pk.test.token',
250+
noCache: true,
251+
zoom: 12,
252+
latitude: 37.7749,
253+
longitude: -122.4194
254+
};
255+
256+
const result = await tool.run(input);
257+
258+
expect(result.isError).toBe(false);
259+
const url = (result.content[0] as { type: 'text'; text: string }).text;
260+
expect(url).toContain('nocache=true');
261+
expect(url).toContain('#12/37.7749/-122.4194');
262+
});
231263
});
232264

233265
describe('metadata', () => {

0 commit comments

Comments
 (0)