UnlockOS Developers
← Back to blog
🛡️

Building Trust Through Testing: Security-First SDK Development

May 4, 2026May 10, 2026
6 min
70 commits
Depth 8/10
testingsecuritytypescriptstate-machinereliability

Building Trust Through Testing: Security-First SDK Development

Introduction

In security-critical systems like smart lock management, comprehensive testing isn't just about catching bugs—it's about building trust. Recent improvements to the UnlockOS SDK demonstrate how systematic testing coverage, security hardening, and robust error handling create a foundation for reliable access control systems.

Comprehensive Testing Strategy

Multi-Layer Test Coverage

The SDK implements a comprehensive testing strategy across multiple layers:

// State machine testing with type safety
interface LockState {
  status: 'locked' | 'unlocked' | 'error' | 'processing';
  lastOperation: string;
  errorCode?: string;
}

describe('Lock State Machine', () => {
  it('should handle unlock sequence with proper error recovery', async () => {
    const stateMachine = createLockStateMachine();
    
    // Test happy path
    await stateMachine.send('UNLOCK');
    expect(stateMachine.state.value).toBe('unlocked');
    
    // Test error recovery
    await stateMachine.send('NETWORK_ERROR');
    expect(stateMachine.state.value).toBe('error');
    expect(stateMachine.state.context.canRetry).toBe(true);
  });
});

Integration Testing for Critical Flows

The checkin-checkout flow receives particular attention with end-to-end testing:

// Integration test for complete access flow
it('should complete full checkin-checkout with proper state persistence', async () => {
  const mockReservation = createMockReservation();
  
  // Test checkin flow
  const checkinResult = await checkinService.processCheckin({
    reservationId: mockReservation.id,
    guestVerification: validGuestData
  });
  
  expect(checkinResult.lockCode).toBeDefined();
  expect(checkinResult.expiresAt).toBeInstanceOf(Date);
  
  // Verify state persistence on refresh (F5 test)
  const persistedState = await stateService.getCheckinState(mockReservation.id);
  expect(persistedState.status).toBe('checked_in');
});

Security Hardening Through Code

Row-Level Security (RLS) Implementation

Database security receives critical attention with tightened RLS policies:

-- Harden reservation_checkins access
CREATE POLICY "checkins_tenant_isolation" ON reservation_checkins
  FOR ALL USING (
    tenant_id = auth.jwt() ->> 'tenant_id'
    AND (auth.jwt() ->> 'role')::text = ANY('{host,admin}'::text[])
  );

-- Secure guest_users with time-based access
CREATE POLICY "guest_access_window" ON guest_users
  FOR SELECT USING (
    tenant_id = auth.jwt() ->> 'tenant_id'
    AND checkin_time <= NOW()
    AND checkout_time >= NOW()
  );

Authentication Bypass Hardening

OTP bypass logic receives security improvements with redundant checks removal:

// Before: Multiple redundant security checks
if (shouldBypassOtp(user) && canBypassOtp(user) && isOtpBypassed(user)) {
  // Redundant checks create confusion
}

// After: Single, clear security boundary
interface AuthBypassConfig {
  readonly enabled: boolean;
  readonly allowedRoles: ReadonlyArray<UserRole>;
  readonly auditLog: boolean;
}

function shouldBypassOtp(user: User, config: AuthBypassConfig): boolean {
  if (!config.enabled) return false;
  
  const hasPermission = config.allowedRoles.includes(user.role);
  
  if (config.auditLog && hasPermission) {
    auditLogger.log('otp_bypass_used', {
      userId: user.id,
      role: user.role,
      timestamp: new Date().toISOString()
    });
  }
  
  return hasPermission;
}

Type Safety and Error Prevention

Preventing Data Coercion Issues

Type safety improvements prevent subtle bugs in price plan management:

// Problem: Implicit string-to-number coercion
interface PricePlanForm {
  name: string; // Could be coerced to number accidentally
  basePrice: number;
}

// Solution: Explicit type guards and validation
interface PricePlanFormSafe {
  readonly name: string;
  readonly basePrice: number;
}

function validatePricePlanForm(input: unknown): PricePlanFormSafe {
  const parsed = PricePlanFormSchema.parse(input);
  
  // Explicit validation prevents coercion
  if (typeof parsed.name !== 'string' || parsed.name.trim().length === 0) {
    throw new ValidationError('Plan name must be a non-empty string');
  }
  
  if (typeof parsed.basePrice !== 'number' || parsed.basePrice < 0) {
    throw new ValidationError('Base price must be a non-negative number');
  }
  
  return parsed;
}

Fee Calculation Robustness

Precise handling of partial-minute billing prevents overcharging:

interface BillingPeriod {
  start: Date;
  end: Date;
  ratePerMinute: number;
}

function calculateUsageFee(period: BillingPeriod): number {
  const totalMinutes = Math.ceil(
    (period.end.getTime() - period.start.getTime()) / (1000 * 60)
  );
  
  // Prevent partial-minute over-billing
  const billableMinutes = Math.max(1, totalMinutes); // Minimum 1 minute
  
  return Number((billableMinutes * period.ratePerMinute).toFixed(2));
}

// Test ensures billing accuracy
it('should not over-bill for partial minutes', () => {
  const period = {
    start: new Date('2026-05-08T10:00:00Z'),
    end: new Date('2026-05-08T10:00:30Z'), // 30 seconds
    ratePerMinute: 1.0
  };
  
  expect(calculateUsageFee(period)).toBe(1.0); // Bills for 1 minute, not fractional
});

Performance with Security

Optimized Queries with Access Control

Database query optimization maintains security boundaries:

// Parallelize queries while maintaining isolation
const [reservation, guestData, lockSettings] = await Promise.all([
  db.reservations
    .select('id', 'property_id', 'status')
    .where('tenant_id', tenantId) // Security boundary maintained
    .where('id', reservationId)
    .first(),
    
  db.guest_users
    .select('id', 'email', 'phone')
    .where('tenant_id', tenantId) // Consistent isolation
    .where('reservation_id', reservationId)
    .first(),
    
  db.lock_settings
    .select('auto_unlock_duration', 'max_attempts')
    .where('tenant_id', tenantId) // Security-first optimization
    .where('property_id', propertyId)
    .first()
]);

Audit and Observability

Measurement Infrastructure

Performance monitoring with security context:

interface SecurityMetrics {
  operationType: 'checkin' | 'checkout' | 'unlock';
  userId: string;
  tenantId: string;
  duration: number;
  success: boolean;
  errorCode?: string;
}

class SecureMetricsCollector {
  private metrics: SecurityMetrics[] = [];
  
  recordOperation(metric: SecurityMetrics): void {
    // Sanitize sensitive data before logging
    const sanitized = {
      ...metric,
      userId: this.hashUserId(metric.userId),
      timestamp: new Date().toISOString()
    };
    
    this.metrics.push(sanitized);
    
    // Alert on suspicious patterns
    if (metric.operationType === 'unlock' && !metric.success) {
      this.checkFailurePattern(metric.userId);
    }
  }
  
  private checkFailurePattern(userId: string): void {
    const recentFailures = this.metrics
      .filter(m => m.userId === userId && !m.success)
      .filter(m => Date.now() - new Date(m.timestamp).getTime() < 300000); // 5 min window
      
    if (recentFailures.length >= 3) {
      securityAlerts.triggerSuspiciousActivity(userId);
    }
  }
}

Testing State Machines

Finite State Machine Validation

// Test state machine transitions comprehensively
describe('Lock State Machine Security', () => {
  it('should prevent invalid state transitions', () => {
    const machine = createLockMachine();
    
    // Test valid transitions
    expect(() => machine.transition('locked', 'UNLOCK')).not.toThrow();
    
    // Test invalid transitions are blocked
    expect(() => machine.transition('error', 'UNLOCK')).toThrow('Invalid transition');
    
    // Test security-critical transitions
    const errorState = machine.transition('unlocked', 'BATTERY_LOW');
    expect(errorState.context.requiresManualIntervention).toBe(true);
  });
});

Summary

Building trust in security-critical systems requires a holistic approach to testing and validation. The UnlockOS SDK demonstrates how comprehensive test coverage, type safety, security hardening, and robust error handling work together to create reliable access control systems. By implementing multi-layer testing strategies, maintaining strict security boundaries, and preventing common pitfalls through type safety, developers can build systems that property managers and guests can trust with their physical security.

The key is treating testing not as an afterthought, but as a fundamental part of security architecture—where every test case is a security assertion and every type guard is a trust boundary.

Key Insights

1
Security

Multi-layer Security Testing

Comprehensive testing strategy covering unit, integration, and E2E tests with security context validation

2
Type Safety

Preventing Coercion Vulnerabilities

Explicit type validation prevents subtle bugs like string-to-number coercion in critical systems

3
State Management

Finite State Machine Validation

State machine testing ensures only valid transitions occur in lock control systems

4
Performance

Secure Query Optimization

Database optimization maintains security boundaries while improving performance through parallelization