@sockethub/data-layer 1.0.0-alpha.4 → 1.0.0-alpha.5
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/API.md +23 -0
- package/QUEUE.md +130 -0
- package/README.md +114 -0
- package/package.json +22 -43
- package/src/credentials-store.test.ts +140 -123
- package/src/credentials-store.ts +116 -68
- package/src/index.ts +27 -3
- package/src/job-base.ts +58 -0
- package/src/job-queue.test.ts +265 -237
- package/src/job-queue.ts +210 -131
- package/src/job-worker.test.ts +128 -0
- package/src/job-worker.ts +97 -0
- package/src/types.ts +21 -23
- package/typedoc.json +11 -0
- package/dist/credentials-store.d.ts +0 -30
- package/dist/credentials-store.js +0 -72
- package/dist/credentials-store.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.js +0 -26
- package/dist/index.js.map +0 -1
- package/dist/job-queue.d.ts +0 -32
- package/dist/job-queue.js +0 -126
- package/dist/job-queue.js.map +0 -1
- package/dist/types.d.ts +0 -29
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
package/API.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# API Documentation
|
|
2
|
+
|
|
3
|
+
This package provides the data layer for Sockethub, including job queue management and secure credential storage.
|
|
4
|
+
|
|
5
|
+
## Main Classes
|
|
6
|
+
|
|
7
|
+
### JobQueue
|
|
8
|
+
Redis-backed job queue for managing ActivityStreams message processing. Creates isolated queues per platform instance and session, providing reliable message delivery and processing coordination between Sockethub server and platform workers.
|
|
9
|
+
|
|
10
|
+
### JobWorker
|
|
11
|
+
Worker for processing jobs from a Redis queue within platform child processes. Connects to the same queue as its corresponding JobQueue instance and processes jobs using a platform-specific handler function.
|
|
12
|
+
|
|
13
|
+
### CredentialsStore
|
|
14
|
+
Secure, encrypted storage for user credentials with session-based isolation. Provides automatic encryption/decryption of credential objects stored in Redis, ensuring that sensitive authentication data is never stored in plaintext.
|
|
15
|
+
|
|
16
|
+
## Complete API Reference
|
|
17
|
+
|
|
18
|
+
For detailed API documentation including method signatures, parameters, and examples, see the [generated TypeDoc documentation](./docs/index.html).
|
|
19
|
+
|
|
20
|
+
## Related Documentation
|
|
21
|
+
|
|
22
|
+
- [Queue Architecture](./QUEUE.md) - Conceptual overview of the queueing system
|
|
23
|
+
- [Main Sockethub Documentation](https://github.com/sockethub/sockethub)
|
package/QUEUE.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Sockethub Queue System
|
|
2
|
+
|
|
3
|
+
The queue system is Sockethub's core infrastructure that enables reliable, scalable
|
|
4
|
+
communication between web clients and protocol platforms.
|
|
5
|
+
|
|
6
|
+
## Why a Queue System?
|
|
7
|
+
|
|
8
|
+
Web applications need to communicate with network protocols (IRC, XMPP, RSS) that:
|
|
9
|
+
|
|
10
|
+
- Have slow or unreliable connections
|
|
11
|
+
- Require persistent state that browsers can't maintain
|
|
12
|
+
- Need to operate across multiple concurrent user sessions
|
|
13
|
+
|
|
14
|
+
The queue system solves this by:
|
|
15
|
+
|
|
16
|
+
- **Decoupling** web clients from protocol complexity
|
|
17
|
+
- **Ensuring reliability** through message persistence
|
|
18
|
+
- **Enabling concurrency** via process isolation
|
|
19
|
+
- **Maintaining security** through encrypted message handling
|
|
20
|
+
|
|
21
|
+
## Architecture Overview
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
25
|
+
│ Web Clients │ │ Sockethub │ │ Protocol │
|
|
26
|
+
│ │ │ Queue System │ │ Platforms │
|
|
27
|
+
│ • Browser Apps │◄──►│ │◄──►│ │
|
|
28
|
+
│ • Mobile Apps │ │ • Job Queues │ │ • IRC Client │
|
|
29
|
+
│ • Desktop Apps │ │ • Message │ │ • XMPP Client │
|
|
30
|
+
│ • APIs │ │ Routing │ │ • RSS Parser │
|
|
31
|
+
│ │ │ • Session Mgmt │ │ • Metadata │
|
|
32
|
+
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Message Flow
|
|
36
|
+
|
|
37
|
+
### 1. Web Request → Queue
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
Web App → Socket.IO → Sockethub Server → Redis Queue
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- Client sends ActivityStreams message
|
|
44
|
+
- Server validates and queues the request
|
|
45
|
+
- Client gets immediate acknowledgment
|
|
46
|
+
|
|
47
|
+
### 2. Queue → Platform Processing
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
Redis Queue → Platform Worker → External Protocol → Response
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
- Platform worker picks up job
|
|
54
|
+
- Processes protocol-specific logic in isolation
|
|
55
|
+
- Connects to external services (IRC, XMPP, etc.)
|
|
56
|
+
|
|
57
|
+
### 3. Response → Client
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
Platform Result → Queue Event → Sockethub Server → Socket.IO → Web App
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- Results flow back through the queue
|
|
64
|
+
- Real-time updates delivered via Socket.IO
|
|
65
|
+
- Clients receive responses and ongoing events
|
|
66
|
+
|
|
67
|
+
## Key Design Principles
|
|
68
|
+
|
|
69
|
+
**Process Isolation**: Each protocol runs in its own child process
|
|
70
|
+
|
|
71
|
+
- Crashed platforms don't affect others
|
|
72
|
+
- Memory leaks and resource issues are contained
|
|
73
|
+
- Different platforms can use different dependencies
|
|
74
|
+
|
|
75
|
+
**Session-Based Queues**: Each client gets dedicated queue infrastructure
|
|
76
|
+
|
|
77
|
+
- Complete privacy between users
|
|
78
|
+
- Natural load distribution
|
|
79
|
+
- Session-specific configurations possible
|
|
80
|
+
|
|
81
|
+
**Message Durability**: All messages persist until processed
|
|
82
|
+
|
|
83
|
+
- Server restarts don't lose messages
|
|
84
|
+
- Failed operations can be retried
|
|
85
|
+
- Audit trail for debugging
|
|
86
|
+
|
|
87
|
+
## Real-World Example
|
|
88
|
+
|
|
89
|
+
A chat application connecting to both IRC and XMPP:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
User message → Queue → [IRC Platform] + [XMPP Platform]
|
|
93
|
+
↓ ↓
|
|
94
|
+
IRC Network XMPP Network
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Benefits:**
|
|
98
|
+
|
|
99
|
+
- Both protocols process concurrently
|
|
100
|
+
- Slow IRC doesn't block XMPP messages
|
|
101
|
+
- Platform crashes only affect that protocol
|
|
102
|
+
- Independent delivery confirmations
|
|
103
|
+
|
|
104
|
+
## Operational Characteristics
|
|
105
|
+
|
|
106
|
+
**Reliability**: Messages are never lost, even during:
|
|
107
|
+
|
|
108
|
+
- Server restarts
|
|
109
|
+
- Platform process crashes
|
|
110
|
+
- Temporary network failures
|
|
111
|
+
- Redis downtime
|
|
112
|
+
|
|
113
|
+
**Scalability**: Natural horizontal scaling:
|
|
114
|
+
|
|
115
|
+
- Multiple server instances share Redis
|
|
116
|
+
- Per-session queue isolation
|
|
117
|
+
- No coordination between servers required
|
|
118
|
+
|
|
119
|
+
**Performance**: Asynchronous design provides:
|
|
120
|
+
|
|
121
|
+
- Immediate web request responses
|
|
122
|
+
- Concurrent protocol processing
|
|
123
|
+
- Efficient resource utilization
|
|
124
|
+
|
|
125
|
+
## Related Documentation
|
|
126
|
+
|
|
127
|
+
- [Data Layer Package README](README.md) - Package overview and usage
|
|
128
|
+
- [Main Sockethub README](../../README.md) - Overall project documentation
|
|
129
|
+
- [BullMQ Documentation](https://bullmq.io/) - Underlying queue library
|
|
130
|
+
- [ActivityStreams Specification](https://www.w3.org/TR/activitystreams-core/) - Message format standard
|
package/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# @sockethub/data-layer
|
|
2
|
+
|
|
3
|
+
Redis-based data storage and job queue infrastructure for Sockethub, providing reliable
|
|
4
|
+
message processing and secure credential storage.
|
|
5
|
+
|
|
6
|
+
## About
|
|
7
|
+
|
|
8
|
+
The data layer package provides the core infrastructure components that enable
|
|
9
|
+
Sockethub's distributed architecture:
|
|
10
|
+
|
|
11
|
+
- **Job Queue System**: Redis-backed message queuing using BullMQ for reliable ActivityStreams processing
|
|
12
|
+
- **Credentials Store**: Encrypted storage for user credentials and authentication data
|
|
13
|
+
- **Session Management**: Per-client data isolation and security
|
|
14
|
+
- **Redis Integration**: Connection management and health checking
|
|
15
|
+
|
|
16
|
+
## Core Components
|
|
17
|
+
|
|
18
|
+
### JobQueue & JobWorker
|
|
19
|
+
|
|
20
|
+
Handles asynchronous message processing between Sockethub server and platform instances.
|
|
21
|
+
The JobQueue manages job creation and queuing on the server side, while JobWorker
|
|
22
|
+
processes jobs within platform child processes.
|
|
23
|
+
|
|
24
|
+
### CredentialsStore
|
|
25
|
+
|
|
26
|
+
Provides secure, encrypted storage for user credentials with automatic
|
|
27
|
+
encryption/decryption and session-based isolation.
|
|
28
|
+
|
|
29
|
+
## Key Features
|
|
30
|
+
|
|
31
|
+
### Security
|
|
32
|
+
|
|
33
|
+
- **End-to-end encryption** of all stored data using AES-256
|
|
34
|
+
- **Session-based isolation** prevents credential cross-contamination
|
|
35
|
+
- **Secure key management** with per-session secrets
|
|
36
|
+
- **No plaintext storage** in Redis or logs
|
|
37
|
+
|
|
38
|
+
### Reliability
|
|
39
|
+
|
|
40
|
+
- **Message persistence** ensures no data loss during processing
|
|
41
|
+
- **Automatic retries** for failed operations
|
|
42
|
+
- **Connection pooling** with Redis for efficiency
|
|
43
|
+
- **Graceful error handling** and recovery
|
|
44
|
+
|
|
45
|
+
### Scalability
|
|
46
|
+
|
|
47
|
+
- **Horizontal scaling** across multiple server instances
|
|
48
|
+
- **Per-session queues** for natural load distribution
|
|
49
|
+
- **Process isolation** for platform-specific processing
|
|
50
|
+
- **Redis clustering** support for high availability
|
|
51
|
+
|
|
52
|
+
## Configuration
|
|
53
|
+
|
|
54
|
+
**Redis Configuration**: Requires a Redis URL in the format `redis://host:port` or `redis://user:pass@host:port`
|
|
55
|
+
|
|
56
|
+
**Dependencies:**
|
|
57
|
+
|
|
58
|
+
- Redis server (6.0+)
|
|
59
|
+
- `@sockethub/crypto` for encryption
|
|
60
|
+
- `@sockethub/schemas` for type definitions
|
|
61
|
+
|
|
62
|
+
## API Documentation
|
|
63
|
+
|
|
64
|
+
For detailed API documentation including method signatures, parameters, and examples,
|
|
65
|
+
generate the API docs from JSDoc comments:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
bun run doc
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Architecture Integration
|
|
72
|
+
|
|
73
|
+
The data layer sits between Sockethub's main server and platform instances:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
[Sockethub Server] ←→ [Data Layer] ←→ [Platform Instances]
|
|
77
|
+
↑ ↑ ↑
|
|
78
|
+
Socket.IO Redis Storage Protocol Logic
|
|
79
|
+
Connections Job Queues (IRC, XMPP, etc.)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Server Integration**: Creates job queues and credential stores per client session
|
|
83
|
+
|
|
84
|
+
**Platform Integration**: Workers process jobs and access stored credentials securely
|
|
85
|
+
|
|
86
|
+
**Redis Storage**: Centralized, persistent storage for all data layer operations
|
|
87
|
+
|
|
88
|
+
## Error Handling
|
|
89
|
+
|
|
90
|
+
The data layer provides comprehensive error handling:
|
|
91
|
+
|
|
92
|
+
- **Connection failures**: Automatic retry with exponential backoff
|
|
93
|
+
- **Encryption errors**: Clear error messages for key management issues
|
|
94
|
+
- **Job failures**: Failed jobs are logged and can be retried
|
|
95
|
+
- **Resource cleanup**: Proper shutdown procedures prevent memory leaks
|
|
96
|
+
|
|
97
|
+
## Performance Considerations
|
|
98
|
+
|
|
99
|
+
**Memory Management:**
|
|
100
|
+
|
|
101
|
+
- Completed jobs are automatically cleaned up
|
|
102
|
+
- Connection pooling minimizes resource usage
|
|
103
|
+
- Per-session isolation prevents memory leaks
|
|
104
|
+
|
|
105
|
+
**Network Efficiency:**
|
|
106
|
+
|
|
107
|
+
- Redis pipelining for batch operations
|
|
108
|
+
- Compressed job payloads for large messages
|
|
109
|
+
- Keep-alive connections reduce overhead
|
|
110
|
+
|
|
111
|
+
## Related Documentation
|
|
112
|
+
|
|
113
|
+
- [Main Sockethub README](../../README.md) - Overall project documentation
|
|
114
|
+
- [Queue System Architecture](QUEUE.md) - Overview of the Queue system
|
package/package.json
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sockethub/data-layer",
|
|
3
3
|
"description": "Storing and RPC of data for Sockethub",
|
|
4
|
-
"version": "1.0.0-alpha.
|
|
4
|
+
"version": "1.0.0-alpha.5",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"private": false,
|
|
6
7
|
"author": "Nick Jennings <nick@silverbucket.net>",
|
|
7
8
|
"license": "MIT",
|
|
8
|
-
"main": "
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"src/"
|
|
13
|
-
],
|
|
9
|
+
"main": "src/index.ts",
|
|
10
|
+
"engines": {
|
|
11
|
+
"bun": ">=1.2"
|
|
12
|
+
},
|
|
14
13
|
"keywords": [
|
|
15
14
|
"sockethub",
|
|
16
15
|
"messaging",
|
|
17
16
|
"redis",
|
|
18
|
-
"data
|
|
17
|
+
"data layer",
|
|
19
18
|
"rpc",
|
|
20
19
|
"data store"
|
|
21
20
|
],
|
|
@@ -25,44 +24,24 @@
|
|
|
25
24
|
"directory": "packages/data-layer"
|
|
26
25
|
},
|
|
27
26
|
"homepage": "https://github.com/sockethub/sockethub/tree/master/packages/data-layer",
|
|
28
|
-
"dependencies": {
|
|
29
|
-
"@sockethub/crypto": "^1.0.0-alpha.4",
|
|
30
|
-
"@sockethub/schemas": "^3.0.0-alpha.4",
|
|
31
|
-
"bull": "^4.0.0",
|
|
32
|
-
"debug": "^4.3.1",
|
|
33
|
-
"secure-store-redis": "^1.4.7"
|
|
34
|
-
},
|
|
35
27
|
"scripts": {
|
|
36
|
-
"
|
|
37
|
-
"clean:deps": "npx rimraf node_modules",
|
|
38
|
-
"compliance": "yarn run lint && yarn run test && yarn run coverage",
|
|
39
|
-
"test": "c8 -x \"src/**/*.test.*\" mocha -r ts-node/register src/*.test.ts",
|
|
40
|
-
"coverage": "c8 check-coverage --statements 85 --branches 90 --functions 100 --lines 85",
|
|
41
|
-
"lint": "eslint \"**/*.ts\"",
|
|
42
|
-
"lint:fix": "eslint --fix \"**/*.ts\"",
|
|
43
|
-
"build": "tsc"
|
|
28
|
+
"doc": "typedoc --options typedoc.json"
|
|
44
29
|
},
|
|
45
|
-
"
|
|
46
|
-
"
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@sockethub/crypto": "1.0.0-alpha.5",
|
|
32
|
+
"@sockethub/schemas": "3.0.0-alpha.5",
|
|
33
|
+
"bullmq": "5.66.4",
|
|
34
|
+
"debug": "4.3.4",
|
|
35
|
+
"ioredis": "5.3.2",
|
|
36
|
+
"secure-store-redis": "3.0.7"
|
|
47
37
|
},
|
|
48
38
|
"devDependencies": {
|
|
49
|
-
"@types/
|
|
50
|
-
"@types/debug": "4.1.
|
|
51
|
-
"@types/
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"@types/sinon": "10.0.13",
|
|
56
|
-
"@typescript-eslint/parser": "5.37.0",
|
|
57
|
-
"c8": "7.12.0",
|
|
58
|
-
"chai": "4.3.6",
|
|
59
|
-
"eslint": "8.23.1",
|
|
60
|
-
"eslint-cli": "1.1.1",
|
|
61
|
-
"mocha": "10.0.0",
|
|
62
|
-
"proxyquire": "2.1.3",
|
|
63
|
-
"sinon": "14.0.0",
|
|
64
|
-
"ts-node": "10.9.1",
|
|
65
|
-
"typescript": "4.8.3"
|
|
39
|
+
"@types/bun": "latest",
|
|
40
|
+
"@types/debug": "4.1.12",
|
|
41
|
+
"@types/sinon": "17.0.2",
|
|
42
|
+
"sinon": "17.0.1",
|
|
43
|
+
"typedoc": "^0.28.14",
|
|
44
|
+
"typedoc-plugin-markdown": "^4.9.0"
|
|
66
45
|
},
|
|
67
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "341ea9eeca6afd1442fe6e01457bc21d112b91a4"
|
|
68
47
|
}
|
|
@@ -1,138 +1,155 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import * as sinon from 'sinon';
|
|
1
|
+
import { beforeEach, describe, expect, it } from "bun:test";
|
|
2
|
+
import * as sinon from "sinon";
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
proxyquire.noCallThru();
|
|
4
|
+
import { CredentialsStore } from "./credentials-store";
|
|
7
5
|
|
|
8
|
-
describe(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
6
|
+
describe("CredentialsStore", () => {
|
|
7
|
+
let credentialsStore,
|
|
8
|
+
MockSecureStore,
|
|
9
|
+
MockStoreGet,
|
|
10
|
+
MockStoreSave,
|
|
11
|
+
MockObjectHash;
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
MockStoreGet = sinon.stub().returns("credential foo");
|
|
14
|
+
MockStoreSave = sinon.stub();
|
|
15
|
+
MockObjectHash = sinon.stub();
|
|
16
|
+
MockSecureStore = sinon.stub().returns({
|
|
17
|
+
get: MockStoreGet,
|
|
18
|
+
save: MockStoreSave,
|
|
19
|
+
});
|
|
20
|
+
class TestCredentialsStore extends CredentialsStore {
|
|
21
|
+
initCrypto() {
|
|
22
|
+
this.objectHash = MockObjectHash;
|
|
23
|
+
}
|
|
24
|
+
initSecureStore(secret, redisConfig) {
|
|
25
|
+
this.store = MockSecureStore({
|
|
26
|
+
namespace: "foo",
|
|
27
|
+
secret: secret,
|
|
28
|
+
redis: redisConfig,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
credentialsStore = new TestCredentialsStore(
|
|
33
|
+
"a parent id",
|
|
34
|
+
"a session id",
|
|
35
|
+
"a secret must be 32 chars and th",
|
|
36
|
+
{ url: "redis config" },
|
|
37
|
+
);
|
|
17
38
|
});
|
|
18
|
-
const StoreMod = proxyquire('./credentials-store', {
|
|
19
|
-
'secure-store-redis': MockSecureStore,
|
|
20
|
-
'@sockethub/crypto': {
|
|
21
|
-
objectHash: MockObjectHash
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
const CredentialsStore = StoreMod.default;
|
|
25
|
-
credentialsStore = new CredentialsStore('a parent id', 'a session id', 'a secret', 'redis config');
|
|
26
|
-
});
|
|
27
39
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
40
|
+
it("returns a valid CredentialsStore object", () => {
|
|
41
|
+
sinon.assert.calledOnce(MockSecureStore);
|
|
42
|
+
sinon.assert.calledWith(MockSecureStore, {
|
|
43
|
+
namespace: "foo",
|
|
44
|
+
secret: "a secret must be 32 chars and th",
|
|
45
|
+
redis: { url: "redis config" },
|
|
46
|
+
});
|
|
47
|
+
expect(typeof credentialsStore).toEqual("object");
|
|
48
|
+
expect(credentialsStore.uid).toEqual(
|
|
49
|
+
`sockethub:data-layer:credentials-store:a parent id:a session id`,
|
|
50
|
+
);
|
|
51
|
+
expect(typeof credentialsStore.get).toEqual("function");
|
|
52
|
+
expect(typeof credentialsStore.save).toEqual("function");
|
|
34
53
|
});
|
|
35
|
-
expect(typeof credentialsStore).to.equal('object');
|
|
36
|
-
expect(credentialsStore.uid).to.equal(`sockethub:data-layer:credentials-store:a parent id:a session id`);
|
|
37
|
-
expect(typeof credentialsStore.get).to.equal('function');
|
|
38
|
-
expect(typeof credentialsStore.save).to.equal('function');
|
|
39
|
-
});
|
|
40
54
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
describe("get", () => {
|
|
56
|
+
it("handles correct params", async () => {
|
|
57
|
+
const res = await credentialsStore.get("an actor");
|
|
58
|
+
sinon.assert.calledOnce(MockStoreGet);
|
|
59
|
+
sinon.assert.calledWith(MockStoreGet, "an actor");
|
|
60
|
+
sinon.assert.notCalled(MockObjectHash);
|
|
61
|
+
sinon.assert.notCalled(MockStoreSave);
|
|
62
|
+
expect(res).toEqual("credential foo");
|
|
63
|
+
});
|
|
50
64
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
65
|
+
it("handles no credentials found", async () => {
|
|
66
|
+
MockStoreGet.returns(undefined);
|
|
67
|
+
expect(async () => {
|
|
68
|
+
await credentialsStore.get("a non-existent actor");
|
|
69
|
+
}).toThrow("credentials not found for a non-existent actor");
|
|
70
|
+
sinon.assert.calledOnce(MockStoreGet);
|
|
71
|
+
sinon.assert.calledWith(MockStoreGet, "a non-existent actor");
|
|
72
|
+
sinon.assert.notCalled(MockObjectHash);
|
|
73
|
+
sinon.assert.notCalled(MockStoreSave);
|
|
74
|
+
});
|
|
60
75
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
it("handles an unexpected error", async () => {
|
|
77
|
+
MockStoreGet.returns(undefined);
|
|
78
|
+
try {
|
|
79
|
+
await credentialsStore.get("a problem actor");
|
|
80
|
+
expect(false).toEqual(true);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
expect(err.toString()).toEqual(
|
|
83
|
+
"Error: credentials not found for a problem actor",
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
sinon.assert.calledOnce(MockStoreGet);
|
|
87
|
+
sinon.assert.calledWith(MockStoreGet, "a problem actor");
|
|
88
|
+
sinon.assert.notCalled(MockObjectHash);
|
|
89
|
+
sinon.assert.notCalled(MockStoreSave);
|
|
90
|
+
});
|
|
76
91
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
92
|
+
it("validates credentialsHash when provided", async () => {
|
|
93
|
+
MockObjectHash.returns("a credentialsHash string");
|
|
94
|
+
MockStoreGet.returns({
|
|
95
|
+
object: "a credential",
|
|
96
|
+
});
|
|
97
|
+
const res = await credentialsStore.get(
|
|
98
|
+
"an actor",
|
|
99
|
+
"a credentialsHash string",
|
|
100
|
+
);
|
|
101
|
+
sinon.assert.calledOnce(MockStoreGet);
|
|
102
|
+
sinon.assert.calledWith(MockStoreGet, "an actor");
|
|
103
|
+
sinon.assert.calledOnce(MockObjectHash);
|
|
104
|
+
sinon.assert.calledWith(MockObjectHash, "a credential");
|
|
105
|
+
sinon.assert.notCalled(MockStoreSave);
|
|
106
|
+
expect(res).toEqual({ object: "a credential" });
|
|
107
|
+
});
|
|
90
108
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
+
it("invalidates credentialsHash when provided", async () => {
|
|
110
|
+
MockObjectHash.returns("the original credentialsHash string");
|
|
111
|
+
MockStoreGet.returns({
|
|
112
|
+
object: "a credential",
|
|
113
|
+
});
|
|
114
|
+
try {
|
|
115
|
+
expect(
|
|
116
|
+
await credentialsStore.get(
|
|
117
|
+
"an actor",
|
|
118
|
+
"a different credentialsHash string",
|
|
119
|
+
),
|
|
120
|
+
).toBeUndefined();
|
|
121
|
+
expect(false).toEqual(true);
|
|
122
|
+
} catch (err) {
|
|
123
|
+
expect(err.toString()).toEqual(
|
|
124
|
+
"Error: invalid credentials for an actor",
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
sinon.assert.calledOnce(MockStoreGet);
|
|
128
|
+
sinon.assert.calledWith(MockStoreGet, "an actor");
|
|
129
|
+
sinon.assert.calledOnce(MockObjectHash);
|
|
130
|
+
sinon.assert.calledWith(MockObjectHash, "a credential");
|
|
131
|
+
sinon.assert.notCalled(MockStoreSave);
|
|
132
|
+
});
|
|
109
133
|
});
|
|
110
|
-
});
|
|
111
134
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
done();
|
|
122
|
-
});
|
|
123
|
-
});
|
|
135
|
+
describe("save", () => {
|
|
136
|
+
it("handles success", async () => {
|
|
137
|
+
const creds = { foo: "bar" };
|
|
138
|
+
await credentialsStore.save("an actor", creds);
|
|
139
|
+
sinon.assert.calledOnce(MockStoreSave);
|
|
140
|
+
sinon.assert.calledWith(MockStoreSave, "an actor", creds);
|
|
141
|
+
sinon.assert.notCalled(MockObjectHash);
|
|
142
|
+
sinon.assert.notCalled(MockStoreGet);
|
|
143
|
+
});
|
|
124
144
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
done();
|
|
135
|
-
});
|
|
145
|
+
it("handles failure", async () => {
|
|
146
|
+
const creds = { foo: "bar" };
|
|
147
|
+
MockStoreSave.returns(undefined);
|
|
148
|
+
await credentialsStore.save("an actor", creds);
|
|
149
|
+
sinon.assert.calledOnce(MockStoreSave);
|
|
150
|
+
sinon.assert.calledWith(MockStoreSave, "an actor", creds);
|
|
151
|
+
sinon.assert.notCalled(MockObjectHash);
|
|
152
|
+
sinon.assert.notCalled(MockStoreGet);
|
|
153
|
+
});
|
|
136
154
|
});
|
|
137
|
-
});
|
|
138
155
|
});
|