aethex-forge/client/lib/onboarding.integration.spec.ts
2025-09-30 22:55:59 +00:00

158 lines
4.2 KiB
TypeScript

import { describe, it, expect, beforeEach, vi, type Mock } from "vitest";
const createStorage = () => {
const store = new Map<string, string>();
const storage: Storage = {
get length() {
return store.size;
},
clear: () => {
store.clear();
},
getItem: (key: string) => store.get(key) ?? null,
key: (index: number) => Array.from(store.keys())[index] ?? null,
removeItem: (key: string) => {
store.delete(key);
},
setItem: (key: string, value: string) => {
store.set(key, value);
},
} as Storage;
return storage;
};
vi.stubGlobal("localStorage", createStorage());
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue({
ok: false,
status: 404,
text: async () => "",
json: async () => ({}),
}),
);
const fetchMock = fetch as unknown as Mock;
vi.mock("@/lib/supabase", () => {
const chain = () => ({
select: () => chain(),
insert: () => ({ data: null, error: null }),
update: () => ({ data: null, error: null }),
delete: () => ({ error: null }),
eq: () => chain(),
order: () => chain(),
limit: () => chain(),
single: () => ({ data: null, error: null }),
});
return {
supabase: {
auth: {
signInWithPassword: vi.fn(),
signInWithOAuth: vi.fn(),
linkIdentity: vi.fn(),
unlinkIdentity: vi.fn(),
signOut: vi.fn(),
getUser: async () => ({ data: { user: null }, error: null }),
getSession: async () => ({ data: { session: null }, error: null }),
onAuthStateChange: () => ({
data: {
subscription: {
unsubscribe: () => {},
},
},
}),
},
from: () => chain(),
channel: () => ({
on: () => ({}),
subscribe: () => ({ unsubscribe: () => {} }),
unsubscribe: () => {},
}),
storage: {
from: () => ({
upload: async () => ({ data: null, error: null }),
getPublicUrl: () => ({ data: { publicUrl: "" } }),
}),
},
},
isSupabaseConfigured: false,
};
});
vi.mock("@/lib/aethex-toast", () => ({
aethexToast: {
success: vi.fn(),
error: vi.fn(),
warning: vi.fn(),
info: vi.fn(),
aethex: vi.fn(),
system: vi.fn(),
},
}));
import { mockAuth } from "@/lib/mock-auth";
import {
aethexUserService,
aethexAchievementService,
checkProfileComplete,
type AethexUserProfile,
} from "@/lib/aethex-database-adapter";
const wait = (ms = 50) => new Promise((resolve) => setTimeout(resolve, ms));
describe("onboarding passport flow", () => {
beforeEach(async () => {
localStorage.clear();
await mockAuth.signOut();
fetchMock.mockReset();
fetchMock.mockResolvedValue({
ok: false,
status: 404,
text: async () => "",
json: async () => ({}),
});
});
it("persists profile setup, links providers, and awards welcome badge", async () => {
const email = `tester+${Date.now()}@example.com`;
const password = "Secret123!";
const authResult = await mockAuth.signInWithPassword(email, password);
expect(authResult.error).toBeNull();
await wait();
const user = authResult.data.user;
expect(user).toBeTruthy();
await aethexUserService.updateProfile(user.id, {
id: user.id,
username: "tester",
full_name: "Tester One",
user_type: "community_member" as AethexUserProfile["user_type"],
experience_level: "intermediate" as AethexUserProfile["experience_level"],
bio: "Building awesome experiences",
});
const hydratedProfile = (await mockAuth.getUserProfile(
user.id as any,
)) as AethexUserProfile;
expect(checkProfileComplete(hydratedProfile as any)).toBe(true);
await mockAuth.linkIdentity({ provider: "github" });
const refreshedUser = (await mockAuth.getUser()).data.user;
expect(
refreshedUser?.identities?.some((id: any) => id.provider === "github"),
).toBe(true);
await aethexAchievementService.checkAndAwardOnboardingAchievement(user.id);
const achievements = await aethexAchievementService.getUserAchievements(
user.id,
);
const welcomeBadge = achievements.find(
(item) => item.name === "Welcome to AeThex",
);
expect(welcomeBadge).toBeTruthy();
});
});