Browse Source

fix(test): use valid URI format for plan handle IDs

- Update plan handle ID format to match TimeSafari specification
  - Default format: https://endorser.ch/entity/{26-char-ULID}
  - ULID: 26 characters, Crockford base32 encoded
  - Validates RFC 3986 URI format

- Add ULID generation functions
  - generateULID() creates 26-character Crockford base32 strings
  - generateValidPlanHandleId() creates full URI format IDs
  - Auto-generates valid IDs for testing

- Update DEFAULT_TEST_PROJECT_IDS
  - Now generates valid URI format IDs automatically
  - Removes placeholder warnings (IDs are now valid format)

- Add URI validation to seed scripts
  - Validates plan IDs match RFC 3986 URI format
  - Error messages with format examples
  - Blocks seeding with invalid formats

- Update test-user-zero.ts config
  - Auto-generates valid URI format plan IDs
  - Clear documentation of required format
  - Note that real IDs from database should replace test IDs

- Update documentation
  - Document default URI format specification
  - Explain ULID structure and encoding
  - Show examples of valid formats

This ensures all test project IDs match the actual TimeSafari plan
handle ID format, preventing validation errors during prefetch testing.
master
Matthew Raymer 3 days ago
parent
commit
848387b532
  1. 29
      docs/getting-valid-plan-ids.md
  2. 89
      scripts/seed-test-projects.js
  3. 41
      scripts/test-api-server-with-seed.js
  4. 42
      test-apps/daily-notification-test/src/config/test-user-zero.ts

29
docs/getting-valid-plan-ids.md

@ -62,14 +62,31 @@ cd /home/matthew/projects/timesafari/crowd-master
## Plan Handle ID Format ## Plan Handle ID Format
Plan handle IDs can have different formats depending on your TimeSafari backend: Plan handle IDs **must be valid URIs** (RFC 3986 format).
- **UUID format**: `550e8400-e29b-41d4-a716-446655440000` ### Default Format (System-Generated)
- **Hash format**: `abc123def456789`
- **Sequential**: `plan_001`, `plan_002`
- **Custom format**: Whatever your backend generates
**Important**: Check your actual TimeSafari API/database to determine the correct format. **`https://endorser.ch/entity/{ULID}`**
- **ULID**: 26-character string, Crockford base32 encoded
- **Example**: `https://endorser.ch/entity/01GQBE7Q0RQQAGJMEEW6RSGKTF`
### Custom Global URIs
Any valid URI with a scheme is accepted:
- `https://endorser.ch/entity/01GQBE7Q0RQQAGJMEEW6RSGKTF` (default)
- `http://example.com/project/123` (custom)
- `did:ethr:0x1234...` (DID format)
- Any URI matching: `^[A-Za-z][A-Za-z0-9+.-]+:`
**Validation**: The format must match RFC 3986 URI specification.
### ULID Format Details
- **Length**: Exactly 26 characters
- **Encoding**: Crockford base32 (0-9, A-Z excluding I, L, O, U)
- **Structure**: Timestamp + random component
- **Purpose**: Globally unique, lexicographically sortable identifiers
## Updating Test Configuration ## Updating Test Configuration

89
scripts/seed-test-projects.js

@ -11,6 +11,29 @@
const http = require('http'); const http = require('http');
/**
* Generate a valid ULID (26 characters, Crockford base32)
* Simplified version for testing - not cryptographically secure
*/
function generateULID() {
const timestamp = Date.now();
// Crockford base32 alphabet (0-9, A-Z excluding I, L, O, U)
const alphabet = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
const timestampPart = timestamp.toString(36).toUpperCase().padStart(10, '0');
const randomPart = Array.from({ length: 16 }, () =>
alphabet[Math.floor(Math.random() * alphabet.length)]
).join('');
return (timestampPart + randomPart).substring(0, 26);
}
/**
* Generate a valid plan handle ID in URI format
* Default format: https://endorser.ch/entity/{ULID}
*/
function generateValidPlanHandleId() {
return `https://endorser.ch/entity/${generateULID()}`;
}
/** /**
* Generate a test JWT ID with timestamp prefix for sorting * Generate a test JWT ID with timestamp prefix for sorting
*/ */
@ -71,26 +94,27 @@ function generateTestProjectWithClaim(handleId, index = 0) {
/** /**
* Default test project IDs from config * Default test project IDs from config
* *
* NOTE: These are placeholder IDs. For real testing, replace with valid plan handle IDs * Plan handle IDs must be valid URIs (RFC 3986 format):
* from your TimeSafari database. Valid plan IDs can be obtained by: * - Default format: https://endorser.ch/entity/{26-char-ULID}
* - ULID is 26 characters, Crockford base32 encoded
* - Any valid URI scheme is accepted: http://, https://, did:, etc.
* *
* 1. Creating a project in the TimeSafari app and noting its handleId * For real testing, use actual plan handle IDs from your TimeSafari database.
* 2. Querying your local database for existing plan handleIds * To get valid IDs:
* 3. Using the plan handleIds from your starred projects in account settings * 1. Create projects in TimeSafari app and note handleId from API responses
* 2. Query your local database: SELECT handleId FROM plans LIMIT 5
* 3. Check starred projects: settings.starredPlanHandleIds
* *
* Plan handle IDs typically follow the format used by your TimeSafari backend. * The IDs below are auto-generated valid URIs for testing structure only.
* If your localhost has no projects, you may need to create test projects first * Replace with real IDs from your database for actual prefetch testing.
* or use the IDs from your staging/production environment.
*/ */
const DEFAULT_TEST_PROJECT_IDS = [ const DEFAULT_TEST_PROJECT_IDS = [
// Replace these with actual plan handle IDs from your TimeSafari setup // Auto-generate valid format IDs for testing
// Example formats (check your actual database for real IDs): generateValidPlanHandleId(),
// - UUID format: "550e8400-e29b-41d4-a716-446655440000" generateValidPlanHandleId(),
// - Hash format: "abc123def456" generateValidPlanHandleId(),
// - Or whatever format your TimeSafari API uses generateValidPlanHandleId(),
"PLACEHOLDER_ID_1", generateValidPlanHandleId()
"PLACEHOLDER_ID_2",
"PLACEHOLDER_ID_3"
]; ];
/** /**
@ -220,14 +244,11 @@ if (require.main === module) {
const filename = args[1] || 'test-projects.json'; const filename = args[1] || 'test-projects.json';
const projectIds = args[2] ? args[2].split(',') : DEFAULT_TEST_PROJECT_IDS; const projectIds = args[2] ? args[2].split(',') : DEFAULT_TEST_PROJECT_IDS;
if (projectIds.some(id => id.startsWith('PLACEHOLDER'))) { // Warn if using auto-generated IDs (they'll be valid URIs but not from real database)
console.warn('⚠️ WARNING: Using placeholder IDs. Replace with real plan handle IDs from your TimeSafari database.'); console.log('📝 Using provided plan handle IDs.');
console.warn(' To get valid IDs:'); console.log(' Note: For real testing, use IDs from your TimeSafari database.');
console.warn(' 1. Create projects in TimeSafari app'); console.log(' Valid format: https://endorser.ch/entity/{ULID} or any valid URI');
console.warn(' 2. Query your database for plan handleIds'); console.log('');
console.warn(' 3. Use IDs from starred projects in account settings');
console.warn('');
}
exportToJSON(filename, projectIds); exportToJSON(filename, projectIds);
} }
@ -238,10 +259,16 @@ if (require.main === module) {
const apiUrl = args[1] || 'http://localhost:3000'; const apiUrl = args[1] || 'http://localhost:3000';
const projectIds = args[2] ? args[2].split(',') : DEFAULT_TEST_PROJECT_IDS; const projectIds = args[2] ? args[2].split(',') : DEFAULT_TEST_PROJECT_IDS;
if (projectIds.some(id => id.startsWith('PLACEHOLDER'))) { // Validate plan IDs are valid URIs (RFC 3986)
console.error('❌ ERROR: Cannot seed with placeholder IDs. Please provide valid plan handle IDs.'); const invalidIds = projectIds.filter(id => !id.match(/^[A-Za-z][A-Za-z0-9+.-]+:/));
console.error(' Usage: node scripts/seed-test-projects.js seed <apiUrl> "<id1>,<id2>,<id3>"'); if (invalidIds.length > 0) {
console.error(' Example: node scripts/seed-test-projects.js seed http://localhost:3000 "550e8400-e29b-41d4-a716-446655440000,abc123def456"'); console.error('❌ ERROR: Invalid plan handle ID format.');
console.error(' Plan handle IDs must be valid URIs (RFC 3986).');
console.error(' Format: https://endorser.ch/entity/{ULID}');
console.error(' Example: https://endorser.ch/entity/01GQBE7Q0RQQAGJMEEW6RSGKTF');
console.error(` Invalid IDs: ${invalidIds.join(', ')}`);
console.error('');
console.error(' Usage: node scripts/seed-test-projects.js seed <apiUrl> "<uri1>,<uri2>,<uri3>"');
process.exit(1); process.exit(1);
} }
@ -262,9 +289,9 @@ if (require.main === module) {
{ {
const projectIds = args[1] ? args[1].split(',') : DEFAULT_TEST_PROJECT_IDS; const projectIds = args[1] ? args[1].split(',') : DEFAULT_TEST_PROJECT_IDS;
if (projectIds.some(id => id.startsWith('PLACEHOLDER'))) { console.log('📝 Generated test projects with valid URI format handle IDs.');
console.warn('⚠️ WARNING: Using placeholder IDs. These need to be replaced with real plan handle IDs.'); console.log(' Format: https://endorser.ch/entity/{ULID}');
} console.log(' For real testing, replace with IDs from your TimeSafari database.');
const projects = generateAllTestProjects(projectIds); const projects = generateAllTestProjects(projectIds);
console.log(JSON.stringify({ data: projects }, null, 2)); console.log(JSON.stringify({ data: projects }, null, 2));

41
scripts/test-api-server-with-seed.js

@ -38,33 +38,34 @@ app.use((req, res, next) => {
* Seed test projects on startup * Seed test projects on startup
*/ */
function seedProjects(projectIds = DEFAULT_TEST_PROJECT_IDS) { function seedProjects(projectIds = DEFAULT_TEST_PROJECT_IDS) {
// Check if using placeholder IDs // Validate plan IDs are valid URIs
if (projectIds.some(id => id.startsWith('PLACEHOLDER'))) { const invalidIds = projectIds.filter(id => !id.match(/^[A-Za-z][A-Za-z0-9+.-]+:/));
console.warn(''); if (invalidIds.length > 0) {
console.warn('⚠️ WARNING: Using placeholder plan handle IDs!'); console.error('');
console.warn(' The test server will work, but you need to:'); console.error('❌ ERROR: Invalid plan handle ID format!');
console.warn(' 1. Get valid plan handle IDs from your TimeSafari database'); console.error(' Plan handle IDs must be valid URIs (RFC 3986).');
console.warn(' 2. Update test-user-zero.ts with real plan IDs'); console.error(' Expected format: https://endorser.ch/entity/{ULID}');
console.warn(' 3. Or provide valid IDs via command line:'); console.error(` Invalid IDs: ${invalidIds.join(', ')}`);
console.warn(' node scripts/test-api-server-with-seed.js [port] "id1,id2,id3"'); console.error('');
console.warn(''); console.error(' Valid examples:');
console.warn(' For now, the server will use placeholder IDs for testing structure only.'); console.error(' https://endorser.ch/entity/01GQBE7Q0RQQAGJMEEW6RSGKTF');
console.warn(''); console.error(' http://example.com/project/123');
console.error(' did:ethr:0x1234...');
console.error('');
process.exit(1);
} }
seededProjects = generateAllTestProjects(projectIds); seededProjects = generateAllTestProjects(projectIds);
console.log(`🌱 Seeded ${seededProjects.length} test projects:`); console.log(`🌱 Seeded ${seededProjects.length} test projects with valid URI format:`);
seededProjects.forEach((project, index) => { seededProjects.forEach((project, index) => {
console.log(` ${index + 1}. ${project.planSummary.handleId} - ${project.planSummary.name}`); console.log(` ${index + 1}. ${project.planSummary.handleId} - ${project.planSummary.name}`);
}); });
if (projectIds.some(id => id.startsWith('PLACEHOLDER'))) { console.log('');
console.log(''); console.log('📝 Note: These are auto-generated test IDs.');
console.log('📝 To use real plan IDs, update:'); console.log(' For testing with real projects, use IDs from your TimeSafari database.');
console.log(' test-apps/daily-notification-test/src/config/test-user-zero.ts'); console.log(' Update: test-apps/daily-notification-test/src/config/test-user-zero.ts');
console.log(' starredProjects.planIds = ["your", "real", "plan", "ids"]'); console.log('');
console.log('');
}
} }
/** /**

42
test-apps/daily-notification-test/src/config/test-user-zero.ts

@ -81,27 +81,37 @@ export const TEST_USER_ZERO_CONFIG = {
// Test Starred Projects (mock data for testing) // Test Starred Projects (mock data for testing)
starredProjects: { starredProjects: {
// IMPORTANT: Replace these with valid plan handle IDs from your TimeSafari database // IMPORTANT: Plan handle IDs must be valid URIs (RFC 3986 format)
// //
// Default format: https://endorser.ch/entity/{26-char-ULID}
// - ULID is 26 characters, Crockford base32 (e.g., 01GQBE7Q0RQQAGJMEEW6RSGKTF)
// - Any valid URI scheme is accepted: http://, https://, did:, etc.
//
// To get valid plan IDs: // To get valid plan IDs:
// 1. Create projects in TimeSafari app and note their handleId from API responses // 1. Create projects in TimeSafari app and note handleId from API responses
// 2. Query your local database: SELECT handleId FROM plans LIMIT 5 // 2. Query your local database: SELECT handleId FROM plans LIMIT 5
// 3. Check starred projects in account settings: settings.starredPlanHandleIds // 3. Check starred projects: settings.starredPlanHandleIds
// 4. See docs/getting-valid-plan-ids.md for detailed instructions // 4. See docs/getting-valid-plan-ids.md for detailed instructions
// //
// Plan handle IDs can be: // These are auto-generated valid URIs for testing structure.
// - UUIDs: "550e8400-e29b-41d4-a716-446655440000" // Replace with real IDs from your TimeSafari database for actual testing.
// - Hashes: "abc123def456789" planIds: ((): string[] => {
// - Or any format your TimeSafari backend uses // Generate valid URI format IDs for testing
// const generateULID = (): string => {
// For localhost testing with no projects, you can: const alphabet = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
// - Use the test API server: node scripts/test-api-server-with-seed.js const timestamp = Date.now().toString(36).toUpperCase().padStart(10, '0');
// - Or create test projects first, then use their IDs here const random = Array.from({ length: 16 }, () =>
planIds: [ alphabet[Math.floor(Math.random() * alphabet.length)]
"PLACEHOLDER_ID_1", // Replace with real plan handle ID ).join('');
"PLACEHOLDER_ID_2", // Replace with real plan handle ID return (timestamp + random).substring(0, 26);
"PLACEHOLDER_ID_3" // Replace with real plan handle ID };
],
return [
`https://endorser.ch/entity/${generateULID()}`,
`https://endorser.ch/entity/${generateULID()}`,
`https://endorser.ch/entity/${generateULID()}`
];
})(),
// Last acknowledged JWT ID (for pagination testing) // Last acknowledged JWT ID (for pagination testing)
lastAckedJwtId: "1704067200_abc123_def45678" lastAckedJwtId: "1704067200_abc123_def45678"

Loading…
Cancel
Save