jira-pat 1.0.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/AGENTS.md +218 -0
- package/README.md +64 -0
- package/backend/.env.example +1 -0
- package/backend/__tests__/getJiraClient.test.js +57 -0
- package/backend/__tests__/issues.test.js +565 -0
- package/backend/__tests__/jiraService.test.js +1127 -0
- package/backend/__tests__/projects.test.js +256 -0
- package/backend/coverage/clover.xml +426 -0
- package/backend/coverage/coverage-final.json +4 -0
- package/backend/coverage/lcov-report/base.css +224 -0
- package/backend/coverage/lcov-report/block-navigation.js +87 -0
- package/backend/coverage/lcov-report/favicon.png +0 -0
- package/backend/coverage/lcov-report/index.html +131 -0
- package/backend/coverage/lcov-report/prettify.css +1 -0
- package/backend/coverage/lcov-report/prettify.js +2 -0
- package/backend/coverage/lcov-report/routes/index.html +131 -0
- package/backend/coverage/lcov-report/routes/issues.js.html +823 -0
- package/backend/coverage/lcov-report/routes/projects.js.html +190 -0
- package/backend/coverage/lcov-report/service/index.html +116 -0
- package/backend/coverage/lcov-report/service/jiraService.js.html +1663 -0
- package/backend/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/backend/coverage/lcov-report/sorter.js +210 -0
- package/backend/coverage/lcov.info +707 -0
- package/backend/index.js +38 -0
- package/backend/jest.config.js +11 -0
- package/backend/package-lock.json +5636 -0
- package/backend/package.json +28 -0
- package/backend/routes/issues.js +246 -0
- package/backend/routes/projects.js +35 -0
- package/backend/service/jiraService.js +526 -0
- package/bin/jira.js +92 -0
- package/frontend/.env.example +1 -0
- package/frontend/coverage/base.css +224 -0
- package/frontend/coverage/block-navigation.js +87 -0
- package/frontend/coverage/clover.xml +559 -0
- package/frontend/coverage/components/CreateIssueModal.jsx.html +592 -0
- package/frontend/coverage/components/IssueDetailPanel.jsx.html +1633 -0
- package/frontend/coverage/components/IssueDrawer.jsx.html +550 -0
- package/frontend/coverage/components/IssueTable.jsx.html +571 -0
- package/frontend/coverage/components/SkeletonComponents.jsx.html +223 -0
- package/frontend/coverage/components/ToastContainer.jsx.html +142 -0
- package/frontend/coverage/components/index.html +191 -0
- package/frontend/coverage/coverage-final.json +14 -0
- package/frontend/coverage/favicon.png +0 -0
- package/frontend/coverage/hooks/index.html +161 -0
- package/frontend/coverage/hooks/useFocusTrap.js.html +262 -0
- package/frontend/coverage/hooks/useIssueDrawer.js.html +1000 -0
- package/frontend/coverage/hooks/useIssuesList.js.html +175 -0
- package/frontend/coverage/hooks/useToasts.js.html +142 -0
- package/frontend/coverage/index.html +161 -0
- package/frontend/coverage/prettify.css +1 -0
- package/frontend/coverage/prettify.js +2 -0
- package/frontend/coverage/services/api.js.html +547 -0
- package/frontend/coverage/services/index.html +116 -0
- package/frontend/coverage/sort-arrow-sprite.png +0 -0
- package/frontend/coverage/sorter.js +210 -0
- package/frontend/coverage/utils/index.html +131 -0
- package/frontend/coverage/utils/issueHelpers.jsx.html +334 -0
- package/frontend/coverage/utils/sanitize.js.html +166 -0
- package/frontend/index.html +13 -0
- package/frontend/package-lock.json +3436 -0
- package/frontend/package.json +30 -0
- package/frontend/src/App.jsx +447 -0
- package/frontend/src/__tests__/components/CreateIssueModal.test.jsx +375 -0
- package/frontend/src/__tests__/components/IssueDetailPanel.test.jsx +962 -0
- package/frontend/src/__tests__/components/IssueDrawer.test.jsx +240 -0
- package/frontend/src/__tests__/components/IssueTable.test.jsx +423 -0
- package/frontend/src/__tests__/components/ToastContainer.test.jsx +196 -0
- package/frontend/src/__tests__/hooks/useFocusTrap.test.js +197 -0
- package/frontend/src/__tests__/hooks/useIssueDrawer.test.js +1053 -0
- package/frontend/src/__tests__/hooks/useIssuesList.test.js +175 -0
- package/frontend/src/__tests__/hooks/useToasts.test.js +110 -0
- package/frontend/src/__tests__/services/api.test.js +568 -0
- package/frontend/src/__tests__/setup.js +54 -0
- package/frontend/src/__tests__/utils/issueHelpers.test.jsx +336 -0
- package/frontend/src/__tests__/utils/sanitize.test.js +238 -0
- package/frontend/src/components/CreateIssueModal.jsx +169 -0
- package/frontend/src/components/ErrorBoundary.jsx +52 -0
- package/frontend/src/components/IssueDetailPanel.jsx +517 -0
- package/frontend/src/components/IssueDrawer.jsx +155 -0
- package/frontend/src/components/IssueTable.jsx +162 -0
- package/frontend/src/components/SkeletonComponents.jsx +46 -0
- package/frontend/src/components/StandaloneIssuePage.jsx +176 -0
- package/frontend/src/components/ToastContainer.jsx +19 -0
- package/frontend/src/hooks/useFocusTrap.js +59 -0
- package/frontend/src/hooks/useIssueDrawer.js +305 -0
- package/frontend/src/hooks/useIssuesList.js +30 -0
- package/frontend/src/hooks/useToasts.js +19 -0
- package/frontend/src/index.css +2070 -0
- package/frontend/src/main.jsx +13 -0
- package/frontend/src/services/api.js +154 -0
- package/frontend/src/utils/issueHelpers.jsx +84 -0
- package/frontend/src/utils/sanitize.js +27 -0
- package/frontend/vite.config.js +15 -0
- package/package.json +19 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# AGENTS.md - Jira Dashboard
|
|
2
|
+
|
|
3
|
+
## Project Structure
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
jira-dashboard/
|
|
7
|
+
├── package.json # Root - runs both servers with concurrently
|
|
8
|
+
├── backend/ # Express API (CommonJS), port 5000
|
|
9
|
+
│ ├── index.js # Entry point
|
|
10
|
+
│ ├── routes/ # API route handlers
|
|
11
|
+
│ └── service/ # Jira API integration (axios + node-cache)
|
|
12
|
+
└── frontend/ # React app (ES modules + Vite), port 5173
|
|
13
|
+
├── src/
|
|
14
|
+
│ ├── App.jsx # Main component (~400 lines)
|
|
15
|
+
│ ├── index.css # All styles (~1700 lines)
|
|
16
|
+
│ ├── services/ # API client (axios)
|
|
17
|
+
│ ├── components/ # React components
|
|
18
|
+
│ │ ├── IssueDrawer.jsx
|
|
19
|
+
│ │ ├── CreateIssueModal.jsx
|
|
20
|
+
│ │ ├── IssueTable.jsx
|
|
21
|
+
│ │ ├── StandaloneIssuePage.jsx
|
|
22
|
+
│ │ └── ToastContainer.jsx
|
|
23
|
+
│ └── hooks/ # Custom React hooks
|
|
24
|
+
│ ├── useIssueDrawer.js
|
|
25
|
+
│ ├── useToasts.js
|
|
26
|
+
│ └── useIssuesList.js
|
|
27
|
+
└── vite.config.js
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Commands
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Root - runs both servers
|
|
34
|
+
npm start
|
|
35
|
+
|
|
36
|
+
# Frontend
|
|
37
|
+
cd frontend && npm run dev # Dev server (port 5173)
|
|
38
|
+
cd frontend && npm run build # Production build
|
|
39
|
+
cd frontend && npm run preview # Preview build
|
|
40
|
+
|
|
41
|
+
# Backend
|
|
42
|
+
cd backend && npm start # Start server (port 5000)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Environment Setup
|
|
46
|
+
|
|
47
|
+
Create `/backend/.env`:
|
|
48
|
+
```
|
|
49
|
+
JIRA_BASE_URL=https://your-domain.atlassian.net
|
|
50
|
+
JIRA_EMAIL=your-email@example.com
|
|
51
|
+
JIRA_PAT=your-api-token
|
|
52
|
+
PORT=5000
|
|
53
|
+
BACKEND_BASE_URL=http://localhost:5000
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Create `/frontend/.env` (optional):
|
|
57
|
+
```
|
|
58
|
+
VITE_API_BASE_URL=http://localhost:5000/api
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Code Style
|
|
62
|
+
|
|
63
|
+
### General
|
|
64
|
+
- No comments unless explaining non-obvious logic
|
|
65
|
+
- Small, focused functions (single responsibility)
|
|
66
|
+
- Prefer early returns to reduce nesting
|
|
67
|
+
- 4-space indentation (backend), standard formatting (frontend)
|
|
68
|
+
|
|
69
|
+
### Frontend (React + Vite - ES Modules)
|
|
70
|
+
- `.jsx` for React components, `.js` for utilities
|
|
71
|
+
- Named exports for utilities, default export for components
|
|
72
|
+
- Functional components with hooks only (no class components)
|
|
73
|
+
- Import order: React → external libs → internal modules → CSS
|
|
74
|
+
|
|
75
|
+
```jsx
|
|
76
|
+
import React, { useState, useEffect } from 'react';
|
|
77
|
+
import { Search, X } from 'lucide-react';
|
|
78
|
+
import { getIssues } from './services/api';
|
|
79
|
+
import './index.css';
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Backend (Node.js - CommonJS)
|
|
83
|
+
- Use `require()` and `module.exports`
|
|
84
|
+
- Keep routes thin, services thick
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
const express = require('express');
|
|
88
|
+
const router = express.Router();
|
|
89
|
+
module.exports = router;
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Naming Conventions
|
|
93
|
+
|
|
94
|
+
| Type | Convention | Example |
|
|
95
|
+
|------|------------|---------|
|
|
96
|
+
| Components | PascalCase | `IssueDrawer` |
|
|
97
|
+
| Functions | camelCase | `fetchIssues` |
|
|
98
|
+
| Constants | UPPER_SNAKE | `STATUS_CHIPS` |
|
|
99
|
+
| CSS Classes | kebab-case | `.issue-table` |
|
|
100
|
+
| Files | kebab-case | `api-client.js` |
|
|
101
|
+
| React state | `useXxx`, `setXxx` | `issues`, `setIssues` |
|
|
102
|
+
|
|
103
|
+
## Common Patterns
|
|
104
|
+
|
|
105
|
+
### Debounce (search inputs)
|
|
106
|
+
```jsx
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
const timer = setTimeout(() => fetchData(), 500);
|
|
109
|
+
return () => clearTimeout(timer);
|
|
110
|
+
}, [searchQuery]);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Async with Loading/Error
|
|
114
|
+
```jsx
|
|
115
|
+
const [loading, setLoading] = useState(false);
|
|
116
|
+
const [error, setError] = useState(null);
|
|
117
|
+
|
|
118
|
+
const fetchData = async () => {
|
|
119
|
+
setLoading(true);
|
|
120
|
+
setError(null);
|
|
121
|
+
try {
|
|
122
|
+
const data = await apiCall();
|
|
123
|
+
setData(data);
|
|
124
|
+
} catch (err) {
|
|
125
|
+
setError(err.message || 'Something went wrong');
|
|
126
|
+
} finally {
|
|
127
|
+
setLoading(false);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Toast Notifications
|
|
133
|
+
```jsx
|
|
134
|
+
const [toasts, setToasts] = useState([]);
|
|
135
|
+
const addToast = (message, type = 'info') => {
|
|
136
|
+
const id = Date.now();
|
|
137
|
+
setToasts(prev => [...prev, { id, message, type }]);
|
|
138
|
+
setTimeout(() => setToasts(prev => prev.filter(t => t.id !== id)), 4000);
|
|
139
|
+
};
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Keyboard Shortcuts
|
|
143
|
+
```jsx
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
const handleKeyDown = (e) => {
|
|
146
|
+
if (e.key === '/' && !['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName)) {
|
|
147
|
+
e.preventDefault();
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
151
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
152
|
+
}, []);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Error Handling
|
|
156
|
+
|
|
157
|
+
### Backend API Routes
|
|
158
|
+
```javascript
|
|
159
|
+
router.get('/:id', async (req, res) => {
|
|
160
|
+
try {
|
|
161
|
+
const data = await service.getData(req.params.id);
|
|
162
|
+
if (!data) return res.status(404).json({ error: 'Not found' });
|
|
163
|
+
res.json(data);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
console.error('Error:', error.message);
|
|
166
|
+
res.status(500).json({ error: 'Server error', details: error.message });
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Frontend
|
|
172
|
+
```jsx
|
|
173
|
+
{error && (
|
|
174
|
+
<div className="error" role="alert">
|
|
175
|
+
<AlertCircle size={16} />
|
|
176
|
+
{error}
|
|
177
|
+
</div>
|
|
178
|
+
)}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Accessibility
|
|
182
|
+
- Always use `aria-label` on icon-only buttons
|
|
183
|
+
- Use semantic HTML (`<button>`, `<nav>`, `<main>`)
|
|
184
|
+
- Support keyboard navigation (Tab, Enter, Escape, Arrow keys)
|
|
185
|
+
|
|
186
|
+
```jsx
|
|
187
|
+
<button onClick={handleDelete} aria-label="Delete item">
|
|
188
|
+
<TrashIcon size={16} />
|
|
189
|
+
</button>
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## CSS Guidelines
|
|
193
|
+
- Use CSS custom properties (variables) for colors, spacing
|
|
194
|
+
- BEM-inspired naming for component styles
|
|
195
|
+
- Avoid inline styles except for dynamic values
|
|
196
|
+
|
|
197
|
+
```css
|
|
198
|
+
:root {
|
|
199
|
+
--primary: #0052cc;
|
|
200
|
+
--spacing-sm: 8px;
|
|
201
|
+
--spacing-md: 16px;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.button { padding: var(--spacing-sm) var(--spacing-md); }
|
|
205
|
+
.button--loading { opacity: 0.7; }
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Jira API Integration
|
|
209
|
+
- Uses axios with 10-second timeout
|
|
210
|
+
- 60-second in-memory cache (node-cache)
|
|
211
|
+
- Basic Auth with Jira PAT for authentication
|
|
212
|
+
- Uses JQL for searching issues
|
|
213
|
+
|
|
214
|
+
## Security
|
|
215
|
+
- Never commit `.env` files (in `.gitignore`)
|
|
216
|
+
- API credentials use Basic Auth with Jira PAT
|
|
217
|
+
- File uploads limited to 10MB
|
|
218
|
+
- Input validation on all API endpoints
|
package/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Jira Dashboard
|
|
2
|
+
|
|
3
|
+
A fast, lightweight, and modern Jira Dashboard built with React, Vite, and Node.js. It avoids CORS and proxy issues by connecting directly to the Jira Cloud API using Basic Authentication within a dedicated Express backend.
|
|
4
|
+
|
|
5
|
+
## Features Currently Supported
|
|
6
|
+
|
|
7
|
+
1. **Direct Jira Cloud Integration**: Uses your Atlassian Email and API Token securely to fetch real-time Jira data.
|
|
8
|
+
2. **"My Assigned Issues" View**: By default, the API automatically fetches tickets currently assigned to you (`currentUser()`).
|
|
9
|
+
3. **Dynamic Filtering**:
|
|
10
|
+
- Filter by **Project Key** (e.g. `ABC`)
|
|
11
|
+
- Search by **Summary / Text** content
|
|
12
|
+
- Filter by **Ticket Status** (To Do, In Progress, Done, Closed)
|
|
13
|
+
4. **Interactive Side Drawer**: Click an issue row to load real-time deep-dive details.
|
|
14
|
+
- **Rich Media & Comments**: Renders descriptions and comments with inline images seamlessly proxied securely from Jira.
|
|
15
|
+
- **Subtasks & Linked Issues**: Visual hierarchy mapping of attached Jira issues, enabling clickable exploration.
|
|
16
|
+
- **Issue Transitions**: Update ticket stages (e.g., "To Do" -> "In Progress") directly within the app.
|
|
17
|
+
- **Ticket Assignment**: Searchable user dropdown directly mapped to your active Jira user directory.
|
|
18
|
+
- **File Attachments**: One-click upload functionality for files mapped directly onto tickets.
|
|
19
|
+
5. **Create New Issues**: Global "+" action triggering dynamic Issue schemas directly synced from Jira projects.
|
|
20
|
+
6. **Fast, Modern UI**: Refactored with `lucide-react` icons, toast notifications, and animated skeleton loaders.
|
|
21
|
+
7. **Keyboard Navigation**: Press `/` to focus search, `Enter` to open an issue, and `Esc` to close modals.
|
|
22
|
+
8. **Backend Caching & Proxying**: 60-second in-memory caching via `node-cache` bypassing CORS/Auth blocks.
|
|
23
|
+
9. **Single Command Start**: Run both the React frontend and Node backend simultaneously using `npm start`.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Project Structure
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
jira-dashboard/
|
|
31
|
+
├── package.json # Root configuration for concurrently running both servers
|
|
32
|
+
├── README.md # This file
|
|
33
|
+
│
|
|
34
|
+
├── backend/ # Node.js + Express API Layer
|
|
35
|
+
│ ├── .env # Secrets (JIRA_BASE_URL, JIRA_EMAIL, JIRA_PAT)
|
|
36
|
+
│ ├── index.js # Main Express server and configuration
|
|
37
|
+
│ ├── package.json # Backend dependencies (express, axios, cors)
|
|
38
|
+
│ ├── routes/
|
|
39
|
+
│ │ └── issues.js # Route logic for /api/issues
|
|
40
|
+
│ └── service/
|
|
41
|
+
│ └── jiraService.js# Jira REST API communication & JQL construction
|
|
42
|
+
│
|
|
43
|
+
└── frontend/ # React + Vite UI
|
|
44
|
+
├── package.json # Frontend dependencies (react, lucide-react)
|
|
45
|
+
├── vite.config.js # Vite configuration
|
|
46
|
+
├── index.html # App entry HTML
|
|
47
|
+
└── src/
|
|
48
|
+
├── main.jsx # React DOM entry point
|
|
49
|
+
├── App.jsx # Main Dashboard View and Filters
|
|
50
|
+
├── index.css # Clean, modern UI Styling
|
|
51
|
+
└── services/
|
|
52
|
+
└── api.js # Axios client connecting to backend /api/issues
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## How To Run
|
|
58
|
+
|
|
59
|
+
1. Open `/backend/.env` and ensure your `JIRA_BASE_URL`, `JIRA_EMAIL`, and `JIRA_PAT` (Atlassian API Token) are filled in.
|
|
60
|
+
2. Run the application from the root directory:
|
|
61
|
+
```bash
|
|
62
|
+
npm start
|
|
63
|
+
```
|
|
64
|
+
3. Open your browser to `http://localhost:5173`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
BACKEND_BASE_URL=http://localhost:5000
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
jest.mock('dotenv', () => ({ config: jest.fn() }));
|
|
2
|
+
jest.mock('node-cache');
|
|
3
|
+
|
|
4
|
+
const NodeCache = require('node-cache');
|
|
5
|
+
const mockCacheInstance = {
|
|
6
|
+
get: jest.fn(),
|
|
7
|
+
set: jest.fn(),
|
|
8
|
+
del: jest.fn(),
|
|
9
|
+
flushAll: jest.fn(),
|
|
10
|
+
keys: jest.fn(() => [])
|
|
11
|
+
};
|
|
12
|
+
NodeCache.mockImplementation(() => mockCacheInstance);
|
|
13
|
+
|
|
14
|
+
describe('getJiraClient config validation', () => {
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
jest.resetModules();
|
|
17
|
+
jest.clearAllMocks();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should throw when JIRA_BASE_URL and JIRA_PAT are not set', async () => {
|
|
21
|
+
delete process.env.JIRA_BASE_URL;
|
|
22
|
+
delete process.env.JIRA_PAT;
|
|
23
|
+
delete process.env.JIRA_EMAIL;
|
|
24
|
+
jest.mock('axios');
|
|
25
|
+
const service = require('../service/jiraService');
|
|
26
|
+
await expect(service.searchIssues({})).rejects.toThrow(
|
|
27
|
+
'JIRA_BASE_URL or JIRA_PAT is not configured in .env'
|
|
28
|
+
);
|
|
29
|
+
process.env.JIRA_BASE_URL = 'https://test.atlassian.net';
|
|
30
|
+
process.env.JIRA_PAT = 'test-pat';
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should use Bearer auth when JIRA_EMAIL is not set', async () => {
|
|
34
|
+
delete process.env.JIRA_EMAIL;
|
|
35
|
+
process.env.JIRA_BASE_URL = 'https://test.atlassian.net';
|
|
36
|
+
process.env.JIRA_PAT = 'test-pat';
|
|
37
|
+
process.env.BACKEND_BASE_URL = 'http://localhost:5000';
|
|
38
|
+
jest.mock('axios');
|
|
39
|
+
const axios = require('axios');
|
|
40
|
+
const mockAxiosInstance = {
|
|
41
|
+
get: jest.fn().mockResolvedValue({ data: { issues: [], total: 0 } }),
|
|
42
|
+
post: jest.fn(),
|
|
43
|
+
put: jest.fn(),
|
|
44
|
+
defaults: { baseURL: 'https://test.atlassian.net' }
|
|
45
|
+
};
|
|
46
|
+
axios.create.mockReturnValue(mockAxiosInstance);
|
|
47
|
+
const service = require('../service/jiraService');
|
|
48
|
+
await service.searchIssues({ project: 'TEST' });
|
|
49
|
+
expect(axios.create).toHaveBeenCalledWith(
|
|
50
|
+
expect.objectContaining({
|
|
51
|
+
headers: expect.objectContaining({
|
|
52
|
+
Authorization: expect.stringMatching(/^Bearer /)
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
});
|