File size: 5,416 Bytes
9bd422a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/**
 * Unit tests for ShareableURL logic
 * Validates: Requirements 25.1, 25.2, 25.3, 25.4, 25.5
 */

import { describe, it, expect, beforeEach } from 'vitest';

// ─── Pure logic extracted from ShareableURL for testability ─────────────

const HASH_PREFIX = '#model=';

/**
 * Parse a hash string and return the model ID, or null.
 * @param {string} hash - e.g. "#model=ppe"
 * @returns {string|null}
 */
function parseHash(hash) {
  if (!hash || !hash.startsWith(HASH_PREFIX)) {
    return null;
  }
  const raw = hash.substring(HASH_PREFIX.length);
  const decoded = decodeURIComponent(raw).trim();
  return decoded || null;
}

/**
 * Build a hash string from a model ID.
 * @param {string} modelId
 * @returns {string}
 */
function buildHash(modelId) {
  return HASH_PREFIX + encodeURIComponent(modelId);
}

/**
 * Find a model by ID in a list.
 * @param {Array<{id: string}>} modelList
 * @param {string} modelId
 * @returns {object|null}
 */
function findModelById(modelList, modelId) {
  if (!modelList || !modelId) return null;
  return modelList.find((m) => m.id === modelId) || null;
}

/**
 * Validate a hash against a model list. Returns the model or an error.
 * @param {string} hash
 * @param {Array<{id: string}>} modelList
 * @returns {{ model: object|null, error: string|null }}
 */
function validateHash(hash, modelList) {
  const modelId = parseHash(hash);
  if (!modelId) {
    return { model: null, error: null };
  }
  const model = findModelById(modelList, modelId);
  if (!model) {
    return {
      model: null,
      error: `Model "${modelId}" not found. The shared link may be outdated or invalid.`
    };
  }
  return { model, error: null };
}

// ─── Test Data ──────────────────────────────────────────────────────────

const SAMPLE_MODELS = [
  { id: 'jsl', name: 'JSL Model', path: 'models/jsl/model.onnx' },
  { id: 'ppe', name: 'PPE Detection Model', path: 'models/ppe/model.onnx' },
  { id: 'vsl', name: 'VSL Model', path: 'models/vsl/model.onnx' },
];

// ─── Tests ──────────────────────────────────────────────────────────────

describe('ShareableURL - parseHash', () => {
  it('should return null for empty hash', () => {
    expect(parseHash('')).toBeNull();
    expect(parseHash(null)).toBeNull();
    expect(parseHash(undefined)).toBeNull();
  });

  it('should return null for hash without model= prefix', () => {
    expect(parseHash('#other=value')).toBeNull();
    expect(parseHash('#')).toBeNull();
    expect(parseHash('#model')).toBeNull();
  });

  it('should parse a valid model hash', () => {
    expect(parseHash('#model=ppe')).toBe('ppe');
    expect(parseHash('#model=jsl')).toBe('jsl');
  });

  it('should decode URI-encoded model IDs', () => {
    expect(parseHash('#model=my%20model')).toBe('my model');
  });

  it('should return null for #model= with empty value', () => {
    expect(parseHash('#model=')).toBeNull();
    expect(parseHash('#model=   ')).toBeNull();
  });
});

describe('ShareableURL - buildHash', () => {
  it('should build a valid hash string', () => {
    expect(buildHash('ppe')).toBe('#model=ppe');
    expect(buildHash('jsl')).toBe('#model=jsl');
  });

  it('should encode special characters', () => {
    const hash = buildHash('my model');
    expect(hash).toBe('#model=my%20model');
  });
});

describe('ShareableURL - round-trip', () => {
  it('should round-trip a model ID through build and parse', () => {
    const ids = ['ppe', 'jsl', 'vsl', 'some-id'];
    ids.forEach((id) => {
      const hash = buildHash(id);
      expect(parseHash(hash)).toBe(id);
    });
  });
});

describe('ShareableURL - findModelById', () => {
  it('should find a model by ID', () => {
    const result = findModelById(SAMPLE_MODELS, 'ppe');
    expect(result).not.toBeNull();
    expect(result.id).toBe('ppe');
  });

  it('should return null for unknown ID', () => {
    expect(findModelById(SAMPLE_MODELS, 'unknown')).toBeNull();
  });

  it('should return null for null/empty inputs', () => {
    expect(findModelById(null, 'ppe')).toBeNull();
    expect(findModelById(SAMPLE_MODELS, null)).toBeNull();
    expect(findModelById(SAMPLE_MODELS, '')).toBeNull();
  });
});

describe('ShareableURL - validateHash', () => {
  it('should return model for valid hash with known ID', () => {
    const { model, error } = validateHash('#model=ppe', SAMPLE_MODELS);
    expect(error).toBeNull();
    expect(model).not.toBeNull();
    expect(model.id).toBe('ppe');
  });

  it('should return error for valid hash with unknown ID (Req 25.4)', () => {
    const { model, error } = validateHash('#model=nonexistent', SAMPLE_MODELS);
    expect(model).toBeNull();
    expect(error).toContain('nonexistent');
    expect(error).toContain('not found');
  });

  it('should return null model and no error for empty hash', () => {
    const { model, error } = validateHash('', SAMPLE_MODELS);
    expect(model).toBeNull();
    expect(error).toBeNull();
  });

  it('should return null model and no error for non-model hash', () => {
    const { model, error } = validateHash('#other=value', SAMPLE_MODELS);
    expect(model).toBeNull();
    expect(error).toBeNull();
  });
});