|
| 1 | +# frozen_string_literal: true |
| 2 | + |
| 3 | +require 'spec_helper' |
| 4 | + |
| 5 | +RSpec.describe Zxcvbn::Data do |
| 6 | + let(:data) { described_class.new } |
| 7 | + |
| 8 | + describe '#initialize' do |
| 9 | + it 'loads ranked dictionaries' do |
| 10 | + expect(data.ranked_dictionaries).to be_a(Hash) |
| 11 | + expect(data.ranked_dictionaries).not_to be_empty |
| 12 | + end |
| 13 | + |
| 14 | + it 'loads all expected dictionaries' do |
| 15 | + expect(data.ranked_dictionaries.keys).to include('english', 'female_names', 'male_names', 'passwords', 'surnames') |
| 16 | + end |
| 17 | + |
| 18 | + it 'loads adjacency graphs' do |
| 19 | + expect(data.adjacency_graphs).to be_a(Hash) |
| 20 | + expect(data.adjacency_graphs).not_to be_empty |
| 21 | + end |
| 22 | + |
| 23 | + it 'loads expected adjacency graphs' do |
| 24 | + expect(data.adjacency_graphs.keys).to include('qwerty', 'dvorak', 'keypad', 'mac_keypad') |
| 25 | + end |
| 26 | + |
| 27 | + it 'builds dictionary tries' do |
| 28 | + expect(data.dictionary_tries).to be_a(Hash) |
| 29 | + expect(data.dictionary_tries).not_to be_empty |
| 30 | + end |
| 31 | + |
| 32 | + it 'builds tries for all dictionaries' do |
| 33 | + expect(data.dictionary_tries.keys).to match_array(data.ranked_dictionaries.keys) |
| 34 | + end |
| 35 | + |
| 36 | + it 'creates Trie objects' do |
| 37 | + data.dictionary_tries.each_value do |trie| |
| 38 | + expect(trie).to be_a(Zxcvbn::Trie) |
| 39 | + end |
| 40 | + end |
| 41 | + |
| 42 | + it 'computes graph statistics' do |
| 43 | + expect(data.graph_stats).to be_a(Hash) |
| 44 | + expect(data.graph_stats).not_to be_empty |
| 45 | + end |
| 46 | + |
| 47 | + it 'computes stats for all graphs' do |
| 48 | + expect(data.graph_stats.keys).to match_array(data.adjacency_graphs.keys) |
| 49 | + end |
| 50 | + |
| 51 | + it 'includes average_degree in graph stats' do |
| 52 | + data.graph_stats.each_value do |stats| |
| 53 | + expect(stats).to have_key(:average_degree) |
| 54 | + expect(stats[:average_degree]).to be_a(Float) |
| 55 | + expect(stats[:average_degree]).to be > 0 |
| 56 | + end |
| 57 | + end |
| 58 | + |
| 59 | + it 'includes starting_positions in graph stats' do |
| 60 | + data.graph_stats.each_value do |stats| |
| 61 | + expect(stats).to have_key(:starting_positions) |
| 62 | + expect(stats[:starting_positions]).to be_a(Integer) |
| 63 | + expect(stats[:starting_positions]).to be > 0 |
| 64 | + end |
| 65 | + end |
| 66 | + end |
| 67 | + |
| 68 | + describe '#ranked_dictionaries' do |
| 69 | + it 'returns dictionaries with word rankings' do |
| 70 | + dict = data.ranked_dictionaries['english'] |
| 71 | + expect(dict).to be_a(Hash) |
| 72 | + expect(dict.values.first).to be_a(Integer) |
| 73 | + end |
| 74 | + |
| 75 | + it 'ranks common words lower (more frequent)' do |
| 76 | + dict = data.ranked_dictionaries['passwords'] |
| 77 | + # Common passwords should have low rank numbers |
| 78 | + expect(dict['password']).to be_a(Integer) |
| 79 | + expect(dict['password']).to be < 100 |
| 80 | + end |
| 81 | + end |
| 82 | + |
| 83 | + describe '#add_word_list' do |
| 84 | + it 'adds a custom dictionary' do |
| 85 | + data.add_word_list('custom', %w[foo bar baz]) |
| 86 | + expect(data.ranked_dictionaries).to have_key('custom') |
| 87 | + end |
| 88 | + |
| 89 | + it 'ranks the custom dictionary' do |
| 90 | + data.add_word_list('custom', %w[foo bar baz]) |
| 91 | + dict = data.ranked_dictionaries['custom'] |
| 92 | + expect(dict['foo']).to be_a(Integer) |
| 93 | + expect(dict['bar']).to be_a(Integer) |
| 94 | + expect(dict['baz']).to be_a(Integer) |
| 95 | + end |
| 96 | + |
| 97 | + it 'builds a trie for the custom dictionary' do |
| 98 | + data.add_word_list('custom', %w[foo bar baz]) |
| 99 | + expect(data.dictionary_tries).to have_key('custom') |
| 100 | + expect(data.dictionary_tries['custom']).to be_a(Zxcvbn::Trie) |
| 101 | + end |
| 102 | + |
| 103 | + it 'makes custom words searchable via trie' do |
| 104 | + data.add_word_list('custom', %w[test]) |
| 105 | + trie = data.dictionary_tries['custom'] |
| 106 | + results = trie.search_prefixes('testing', 0) |
| 107 | + expect(results).not_to be_empty |
| 108 | + expect(results.first[0]).to eq('test') |
| 109 | + end |
| 110 | + |
| 111 | + it 'handles empty word lists' do |
| 112 | + data.add_word_list('empty', []) |
| 113 | + expect(data.ranked_dictionaries['empty']).to be_empty |
| 114 | + end |
| 115 | + end |
| 116 | + |
| 117 | + describe '#graph_stats' do |
| 118 | + it 'has correct values for qwerty keyboard' do |
| 119 | + stats = data.graph_stats['qwerty'] |
| 120 | + expect(stats[:average_degree]).to be_within(0.01).of(4.6) |
| 121 | + expect(stats[:starting_positions]).to eq(94) |
| 122 | + end |
| 123 | + |
| 124 | + it 'has correct values for keypad' do |
| 125 | + stats = data.graph_stats['keypad'] |
| 126 | + expect(stats[:average_degree]).to be_within(0.01).of(5.07) |
| 127 | + expect(stats[:starting_positions]).to eq(15) |
| 128 | + end |
| 129 | + end |
| 130 | +end |
0 commit comments