-
Notifications
You must be signed in to change notification settings - Fork 21
feat: adding redis APL #374
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 13 commits
ed8a77d
874e1bb
c6b584b
52090dc
02fb495
45e409a
f4f6167
3c27022
b7b6cd1
a078efb
2c24b25
b555ac1
0c56265
2df0a40
4dd1568
55a4c56
3fa3866
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@saleor/app-sdk": minor | ||
| --- | ||
|
|
||
| Adding REDIS as APL provider |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| export * from "./apl"; | ||
| export * from "./env-apl"; | ||
| export * from "./file-apl"; | ||
| export * from "./redis"; | ||
|
||
| export * from "./saleor-cloud"; | ||
| export * from "./upstash-apl"; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| import { RedisAPL } from "./redis-apl"; | ||
|
|
||
| export { RedisAPL }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,197 @@ | ||
| import { createClient } from "redis"; | ||
| import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; | ||
|
|
||
| import { AuthData } from "../apl"; | ||
| import { RedisAPL } from "./redis-apl"; | ||
|
|
||
| // Create a variable to control the connection state | ||
| let isRedisOpen = false; | ||
JannikZed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Create properly typed mock functions | ||
| const mockHGet = vi.fn().mockResolvedValue(undefined); | ||
| const mockHSet = vi.fn().mockResolvedValue(1); | ||
| const mockHDel = vi.fn().mockResolvedValue(1); | ||
| const mockHGetAll = vi.fn().mockResolvedValue({}); | ||
| const mockPing = vi.fn().mockResolvedValue("PONG"); | ||
| const mockConnect = vi.fn().mockImplementation(async () => { | ||
| isRedisOpen = true; | ||
| }); | ||
| const mockDisconnect = vi.fn().mockImplementation(async () => { | ||
| isRedisOpen = false; | ||
| }); | ||
|
|
||
| const mockRedisClient = { | ||
| connect: mockConnect, | ||
| ping: mockPing, | ||
| hGet: mockHGet, | ||
| hSet: mockHSet, | ||
| hDel: mockHDel, | ||
| hGetAll: mockHGetAll, | ||
| get isOpen() { | ||
| return isRedisOpen; | ||
| }, | ||
| disconnect: mockDisconnect, | ||
| isReady: false, | ||
| }; | ||
|
|
||
| vi.mock("redis", () => ({ | ||
JannikZed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| createClient: vi.fn(() => mockRedisClient), | ||
| })); | ||
|
|
||
| describe("RedisAPL", () => { | ||
| const mockHashKey = "test_hash_key"; | ||
| const mockAuthData: AuthData = { | ||
| token: "test-token", | ||
| saleorApiUrl: "https://test-store.saleor.cloud/graphql/", | ||
| appId: "test-app-id", | ||
| }; | ||
|
|
||
| let apl: RedisAPL; | ||
|
|
||
| beforeEach(() => { | ||
| apl = new RedisAPL({ | ||
| client: mockRedisClient, | ||
| hashCollectionKey: mockHashKey, | ||
| }); | ||
| vi.clearAllMocks(); | ||
| isRedisOpen = false; | ||
| }); | ||
|
|
||
| afterEach(async () => { | ||
| if (mockRedisClient.isOpen) { | ||
JannikZed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| await mockRedisClient.disconnect(); | ||
| } | ||
| vi.clearAllMocks(); | ||
| }); | ||
|
|
||
| describe("constructor", () => { | ||
| it("uses provided hash key", async () => { | ||
| const customHashKey = "custom_hash"; | ||
| const customApl = new RedisAPL({ | ||
| client: mockRedisClient, | ||
| hashCollectionKey: customHashKey, | ||
| }); | ||
|
|
||
| await customApl.get(mockAuthData.saleorApiUrl); | ||
| expect(mockRedisClient.hGet).toHaveBeenCalledWith(customHashKey, mockAuthData.saleorApiUrl); | ||
| }); | ||
|
|
||
| it("uses default hash key when not provided", async () => { | ||
| const defaultApl = new RedisAPL({ | ||
| client: mockRedisClient, | ||
| }); | ||
|
|
||
| await defaultApl.get(mockAuthData.saleorApiUrl); | ||
| expect(mockRedisClient.hGet).toHaveBeenCalledWith( | ||
| "saleor_app_auth", | ||
| mockAuthData.saleorApiUrl | ||
| ); | ||
| }); | ||
| }); | ||
|
|
||
| describe("get", () => { | ||
| it("returns undefined when no data found", async () => { | ||
| mockHGet.mockResolvedValueOnce(null); | ||
| const result = await apl.get(mockAuthData.saleorApiUrl); | ||
| expect(result).toBeUndefined(); | ||
| expect(mockConnect).toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it("returns parsed auth data when found", async () => { | ||
| mockHGet.mockResolvedValueOnce(JSON.stringify(mockAuthData)); | ||
| const result = await apl.get(mockAuthData.saleorApiUrl); | ||
| expect(result).toEqual(mockAuthData); | ||
| expect(mockHGet).toHaveBeenCalledWith(mockHashKey, mockAuthData.saleorApiUrl); | ||
| }); | ||
|
|
||
| it("throws error when Redis operation fails", async () => { | ||
| mockHGet.mockRejectedValueOnce(new Error("Redis error")); | ||
| await expect(apl.get(mockAuthData.saleorApiUrl)).rejects.toThrow("Redis error"); | ||
| }); | ||
| }); | ||
|
|
||
| describe("set", () => { | ||
| it("successfully sets auth data", async () => { | ||
| mockHSet.mockResolvedValueOnce(1); | ||
| await apl.set(mockAuthData); | ||
| expect(mockHSet).toHaveBeenCalledWith( | ||
| mockHashKey, | ||
| mockAuthData.saleorApiUrl, | ||
| JSON.stringify(mockAuthData) | ||
| ); | ||
| }); | ||
|
|
||
| it("throws error when Redis operation fails", async () => { | ||
| mockHSet.mockRejectedValueOnce(new Error("Redis error")); | ||
| await expect(apl.set(mockAuthData)).rejects.toThrow("Redis error"); | ||
| }); | ||
| }); | ||
|
|
||
| describe("delete", () => { | ||
| it("successfully deletes auth data", async () => { | ||
| mockHDel.mockResolvedValueOnce(1); | ||
| await apl.delete(mockAuthData.saleorApiUrl); | ||
| expect(mockHDel).toHaveBeenCalledWith(mockHashKey, mockAuthData.saleorApiUrl); | ||
| }); | ||
|
|
||
| it("throws error when Redis operation fails", async () => { | ||
| mockHDel.mockRejectedValueOnce(new Error("Redis error")); | ||
| await expect(apl.delete(mockAuthData.saleorApiUrl)).rejects.toThrow("Redis error"); | ||
| }); | ||
| }); | ||
|
|
||
| describe("getAll", () => { | ||
| it("returns all auth data", async () => { | ||
| const mockAllData = { | ||
| [mockAuthData.saleorApiUrl]: JSON.stringify(mockAuthData), | ||
| }; | ||
| mockHGetAll.mockResolvedValueOnce(mockAllData); | ||
| const result = await apl.getAll(); | ||
| expect(result).toEqual([mockAuthData]); | ||
| }); | ||
|
|
||
| it("throws error when Redis operation fails", async () => { | ||
| mockHGetAll.mockRejectedValueOnce(new Error("Redis error")); | ||
| await expect(apl.getAll()).rejects.toThrow("Redis error"); | ||
| }); | ||
| }); | ||
|
|
||
| describe("isReady", () => { | ||
| it("returns ready true when Redis is connected", async () => { | ||
| mockPing.mockResolvedValueOnce("PONG"); | ||
| const result = await apl.isReady(); | ||
| expect(result).toEqual({ ready: true }); | ||
| }); | ||
|
|
||
| it("returns ready false with error when Redis is not connected", async () => { | ||
| mockPing.mockRejectedValueOnce(new Error("Connection failed")); | ||
| const result = await apl.isReady(); | ||
| expect(result).toEqual({ ready: false, error: new Error("Connection failed") }); | ||
| }); | ||
| }); | ||
|
|
||
| describe("isConfigured", () => { | ||
| it("returns configured true when Redis is connected", async () => { | ||
| mockPing.mockResolvedValueOnce("PONG"); | ||
| const result = await apl.isConfigured(); | ||
| expect(result).toEqual({ configured: true }); | ||
| }); | ||
|
|
||
| it("returns configured false with error when Redis is not connected", async () => { | ||
| mockPing.mockRejectedValueOnce(new Error("Connection failed")); | ||
| const result = await apl.isConfigured(); | ||
| expect(result).toEqual({ configured: false, error: new Error("Connection failed") }); | ||
| }); | ||
| }); | ||
|
|
||
| /** | ||
| * Type compatibility test with real Redis client | ||
| */ | ||
| describe("RedisAPL type compatibility", () => { | ||
| it("should accept Redis client type", () => { | ||
| const client = createClient(); | ||
| // This test is just for TypeScript to verify the types | ||
| expect(() => new RedisAPL({ client, hashCollectionKey: "test" })).not.toThrow(); | ||
| }); | ||
| }); | ||
| }); | ||
Uh oh!
There was an error while loading. Please reload this page.